diff options
Diffstat (limited to 'python/psutil')
90 files changed, 30418 insertions, 0 deletions
diff --git a/python/psutil/CREDITS b/python/psutil/CREDITS new file mode 100644 index 0000000000..170751b0a3 --- /dev/null +++ b/python/psutil/CREDITS @@ -0,0 +1,310 @@ +Intro +===== + +I would like to recognize some of the people who have been instrumental in the +development of psutil. +I'm sure I'm forgetting some people (feel free to email me), but here is a +short list. +It's modeled after the Linux CREDITS file where the fields are: +name (N), e-mail (E), web-address (W), country (C), description (D), (I) issues +(issue tracker is at https://github.com/giampaolo/psutil/issues). +Really thanks to all of you. + +- Giampaolo + +Author +====== + +N: Giampaolo Rodola' +C: Italy +E: g.rodola@gmail.com +W: http://grodola.blogspot.com/ + +Contributors +============ + +N: Jay Loden +C: NJ, USA +E: jloden@gmail.com +D: original co-author, initial design/bootstrap and occasional bug fixes +W: http://www.jayloden.com + +N: Jeremy Whitlock +E: jcscoobyrs@gmail.com +D: great help with OSX C development. +I: 125, 150, 174, 206 + +N: wj32 +E: wj32.64@gmail.com +D: process username() and get_connections() on Windows +I: 114, 115 + +N: Yan Raber +C: Bologna, Italy +E: yanraber@gmail.com +D: help on Windows development (initial version of Process.username()) + +N: Justin Venus +E: justin.venus@gmail.com +D: Solaris support +I: 18 + +N: Dave Daeschler +C: USA +E: david.daeschler@gmail.com +W: http://daviddaeschler.com +D: some contributions to initial design/bootstrap plus occasional bug fixing +I: 522, 536 + +N: cjgohlke +E: cjgohlke@gmail.com +D: Windows 64 bit support +I: 107 + +N: Jeffery Kline +E: jeffery.kline@gmail.com +I: 130 + +N: Grabriel Monnerat +E: gabrielmonnerat@gmail.com +I: 146 + +N: Philip Roberts +E: philip.roberts@gmail.com +I: 168 + +N: jcscoobyrs +E: jcscoobyrs@gmail.com +I: 125 + +N: Sandro Tosi +E: sandro.tosi@gmail.com +I: 200, 201 + +N: Andrew Colin +E: andrew.colin@gmail.com +I: 248 + +N: Amoser +E: amoser@google.com +I: 266, 267, 340 + +N: Matthew Grant +E: matthewgrant5@gmail.com +I: 271 + +N: oweidner +E: oweidner@cct.lsu.edu +I: 275 + +N: Tarek Ziade +E: ziade.tarek +I: 281 + +N: Luca Cipriani +C: Turin, Italy +E: luca.opensource@gmail.com +I: 278 + +N: Maciej Lach, +E: maciej.lach@gmail.com +I: 294 + +N: James Pye +E: james.pye@gmail.com +I: 305, 306 + +N: Stanchev Emil +E: stanchev.emil +I: 314 + +N: Kim Gräsman +E: kim.grasman@gmail.com +D: ...also kindly donated some money. +I: 316 + +N: Riccardo Murri +C: Italy +I: 318 + +N: Florent Xicluna +E: florent.xicluna@gmail.com +I: 319 + +N: Michal Spondr +E: michal.spondr +I: 313 + +N: Jean Sebastien +E: dumbboules@gmail.com +I: 344 + +N: Rob Smith +W: http://www.kormoc.com/ +I: 341 + +N: Youngsik Kim +W: https://plus.google.com/101320747613749824490/ +I: 317 + +N: Gregory Szorc +W: https://plus.google.com/116873264322260110710/posts +I: 323 + +N: André Oriani +E: aoriani@gmail.com +I: 361 + +N: clackwell +E: clackwell@gmail.com +I: 356 + +N: m.malycha +E: m.malycha@gmail.com +I: 351 + +N: John Baldwin +E: jhb@FreeBSD.org +I: 370 + +N: Jan Beich +E: jbeich@tormail.org +I: 325 + +N: floppymaster +E: floppymaster@gmail.com +I: 380 + +N: Arfrever.FTA +E: Arfrever.FTA@gmail.com +I: 369, 404 + +N: danudey +E: danudey@gmail.com +I: 386 + +N: Adrien Fallou +I: 224 + +N: Gisle Vanem +E: gisle.vanem@gmail.com +I: 411 + +N: thepyr0 +E: thepyr0@gmail.com +I: 414 + +N: John Pankov +E: john.pankov@gmail.com +I: 435 + +N: Matt Good +W: http://matt-good.net/ +I: 438 + +N: Ulrich Klank +E: ulrich.klank@scitics.de +I: 448 + +N: Josiah Carlson +E: josiah.carlson@gmail.com +I: 451, 452 + +N: Raymond Hettinger +D: namedtuple and lru_cache backward compatible implementations. + +N: Jason Kirtland +D: backward compatible implementation of collections.defaultdict. + +M: Ken Seeho +D: @cached_property decorator + +N: crusaderky +E: crusaderky@gmail.com +I: 470, 477 + +E: alex@mroja.net +I: 471 + +N: Gautam Singh +E: gautam.singh@gmail.com +I: 466 + +E: lhn@hupfeldtit.dk +I: 476, 479 + +N: Francois Charron +E: francois.charron.1@gmail.com +I: 474 + +N: Naveed Roudsari +E: naveed.roudsari@gmail.com +I: 421 + +N: Alexander Grothe +E: Alexander.Grothe@gmail.com +I: 497 + +N: Szigeti Gabor Niif +E: szigeti.gabor.niif@gmail.com +I: 446 + +N: msabramo +E: msabramo@gmail.com +I: 492 + +N: Jeff Tang +W: https://github.com/mrjefftang +I: 340, 529, 616, 653, 654 + +N: Yaolong Huang +E: airekans@gmail.com +W: http://airekans.github.io/ +I: 530 + +N: Anders Chrigström +W: https://github.com/anders-chrigstrom +I: 496 + +N: spacewander +E: spacewanderlzx@gmail.com +I: 561 + +N: Sylvain Mouquet +E: sylvain.mouquet@gmail.com +I: 565 + +N: karthikrev +I: 568 + +N: Bruno Binet +E: bruno.binet@gmail.com +I: 572 + +N: Gabi Davar +C: Israel +W: https://github.com/mindw +I: 578, 581, 587 + +N: spacewanderlzx +C: Guangzhou,China +E: spacewanderlzx@gmail.com +I: 555 + +N: Fabian Groffen +I: 611, 618 + +N: desbma +W: https://github.com/desbma +C: France +I: 628 + +N: John Burnett +W: http://www.johnburnett.com/ +C: Irvine, CA, US +I: 614 + +N: Árni Már Jónsson +E: Reykjavik, Iceland +E: https://github.com/arnimarj +I: 634 diff --git a/python/psutil/HISTORY.rst b/python/psutil/HISTORY.rst new file mode 100644 index 0000000000..12b985d1e9 --- /dev/null +++ b/python/psutil/HISTORY.rst @@ -0,0 +1,1018 @@ +Bug tracker at https://github.com/giampaolo/psutil/issues + +3.1.1 - 2015-07-15 +================== + +**Bug fixes** + +- #645: [Linux] psutil.cpu_times_percent() may produce negative results. +- #656: 'from psutil import *' does not work. + + +3.1.0 - 2015-07-15 +================== + +**Enhancements** + +- #534: [Linux] disk_partitions() added support for ZFS filesystems. +- #646: continuous tests integration for Windows with + https://ci.appveyor.com/project/giampaolo/psutil. +- #647: new dev guide: + https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst +- #651: continuous code quality test integration with + https://scrutinizer-ci.com/g/giampaolo/psutil/ + +**Bug fixes** + +- #340: [Windows] Process.open_files() no longer hangs. Instead it uses a + thred which times out and skips the file handle in case it's taking too long + to be retrieved. (patch by Jeff Tang, PR #597) +- #627: [Windows] Process.name() no longer raises AccessDenied for pids owned + by another user. +- #636: [Windows] Process.memory_info() raise AccessDenied. +- #637: [UNIX] raise exception if trying to send signal to Process PID 0 as it + will affect os.getpid()'s process group instead of PID 0. +- #639: [Linux] Process.cmdline() can be truncated. +- #640: [Linux] *connections functions may swallow errors and return an + incomplete list of connnections. +- #642: repr() of exceptions is incorrect. +- #653: [Windows] Add inet_ntop function for Windows XP to support IPv6. +- #641: [Windows] Replace deprecated string functions with safe equivalents. + + +3.0.1 - 2015-06-18 +================== + +**Bug fixes** + +- #632: [Linux] better error message if cannot parse process UNIX connections. +- #634: [Linux] Proces.cmdline() does not include empty string arguments. +- #635: [UNIX] crash on module import if 'enum' package is installed on python + < 3.4. + + +3.0.0 - 2015-06-13 +================== + +**Enhancements** + +- #250: new psutil.net_if_stats() returning NIC statistics (isup, duplex, + speed, MTU). +- #376: new psutil.net_if_addrs() returning all NIC addresses a-la ifconfig. +- #469: on Python >= 3.4 ``IOPRIO_CLASS_*`` and ``*_PRIORITY_CLASS`` constants + returned by psutil.Process' ionice() and nice() methods are enums instead of + plain integers. +- #581: add .gitignore. (patch by Gabi Davar) +- #582: connection constants returned by psutil.net_connections() and + psutil.Process.connections() were turned from int to enums on Python > 3.4. +- #587: Move native extension into the package. +- #589: Process.cpu_affinity() accepts any kind of iterable (set, tuple, ...), + not only lists. +- #594: all deprecated APIs were removed. +- #599: [Windows] process name() can now be determined for all processes even + when running as a limited user. +- #602: pre-commit GIT hook. +- #629: enhanced support for py.test and nose test discovery and tests run. +- #616: [Windows] Add inet_ntop function for Windows XP. + +**Bug fixes** + +- #428: [all UNIXes except Linux] correct handling of zombie processes; + introduced new ZombieProcess exception class. +- #512: [BSD] fix segfault in net_connections(). +- #555: [Linux] psutil.users() correctly handles ":0" as an alias for + "localhost" +- #579: [Windows] Fixed open_files() for PID>64K. +- #579: [Windows] fixed many compiler warnings. +- #585: [FreeBSD] net_connections() may raise KeyError. +- #586: [FreeBSD] cpu_affinity() segfaults on set in case an invalid CPU + number is provided. +- #593: [FreeBSD] Process().memory_maps() segfaults. +- #606: Process.parent() may swallow NoSuchProcess exceptions. +- #611: [SunOS] net_io_counters has send and received swapped +- #614: [Linux]: cpu_count(logical=False) return the number of physical CPUs + instead of physical cores. +- #618: [SunOS] swap tests fail on Solaris when run as normal user +- #628: [Linux] Process.name() truncates process name in case it contains + spaces or parentheses. + + +2.2.1 - 2015-02-02 +================== + +**Bug fixes** + +- #496: [Linux] fix "ValueError: ambiguos inode with multiple PIDs references" + (patch by Bruno Binet) + + +2.2.0 - 2015-01-06 +================== + +**Enhancements** + +- #521: drop support for Python 2.4 and 2.5. +- #553: new examples/pstree.py script. +- #564: C extension version mismatch in case the user messed up with psutil + installation or with sys.path is now detected at import time. +- #568: New examples/pidof.py script. +- #569: [FreeBSD] add support for process CPU affinity. + +**Bug fixes** + +- #496: [Solaris] can't import psutil. +- #547: [UNIX] Process.username() may raise KeyError if UID can't be resolved. +- #551: [Windows] get rid of the unicode hack for net_io_counters() NIC names. +- #556: [Linux] lots of file handles were left open. +- #561: [Linux] net_connections() might skip some legitimate UNIX sockets. + (patch by spacewander) +- #565: [Windows] use proper encoding for psutil.Process.username() and + psutil.users(). (patch by Sylvain Mouquet) +- #567: [Linux] in the alternative implementation of CPU affinity PyList_Append + and Py_BuildValue return values are not checked. +- #569: [FreeBSD] fix memory leak in psutil.cpu_count(logical=False). +- #571: [Linux] Process.open_files() might swallow AccessDenied exceptions and + return an incomplete list of open files. + + +2.1.3 - 2014-09-26 +================== + +- #536: [Linux]: fix "undefined symbol: CPU_ALLOC" compilation error. + + +2.1.2 - 2014-09-21 +================== + +**Enhancements** + +- #407: project moved from Google Code to Github; code moved from Mercurial + to Git. +- #492: use tox to run tests on multiple python versions. (patch by msabramo) +- #505: [Windows] distribution as wheel packages. +- #511: new examples/ps.py sample code. + +**Bug fixes** + +- #340: [Windows] Process.get_open_files() no longer hangs. (patch by + Jeff Tang) +- #501: [Windows] disk_io_counters() may return negative values. +- #503: [Linux] in rare conditions Process exe(), open_files() and + connections() methods can raise OSError(ESRCH) instead of NoSuchProcess. +- #504: [Linux] can't build RPM packages via setup.py +- #506: [Linux] python 2.4 support was broken. +- #522: [Linux] Process.cpu_affinity() might return EINVAL. (patch by David + Daeschler) +- #529: [Windows] Process.exe() may raise unhandled WindowsError exception + for PIDs 0 and 4. (patch by Jeff Tang) +- #530: [Linux] psutil.disk_io_counters() may crash on old Linux distros + (< 2.6.5) (patch by Yaolong Huang) +- #533: [Linux] Process.memory_maps() may raise TypeError on old Linux distros. + + +2.1.1 - 2014-04-30 +================== + +**Bug fixes** + +- #446: [Windows] fix encoding error when using net_io_counters() on Python 3. + (patch by Szigeti Gabor Niif) +- #460: [Windows] net_io_counters() wraps after 4G. +- #491: [Linux] psutil.net_connections() exceptions. (patch by Alexander Grothe) + + +2.1.0 - 2014-04-08 +================== + +**Enhancements** + +- #387: system-wide open connections a-la netstat. + +**Bug fixes** + +- #421: [Solaris] psutil does not compile on SunOS 5.10 (patch by Naveed + Roudsari) +- #489: [Linux] psutil.disk_partitions() return an empty list. + + +2.0.0 - 2014-03-10 +================== + +**Enhancements** + +- #424: [Windows] installer for Python 3.X 64 bit. +- #427: number of logical and physical CPUs (psutil.cpu_count()). +- #447: psutil.wait_procs() timeout parameter is now optional. +- #452: make Process instances hashable and usable with set()s. +- #453: tests on Python < 2.7 require unittest2 module. +- #459: add a make file for running tests and other repetitive tasks (also + on Windows). +- #463: make timeout parameter of cpu_percent* functions default to 0.0 'cause + it's a common trap to introduce slowdowns. +- #468: move documentation to readthedocs.com. +- #477: process cpu_percent() is about 30% faster. (suggested by crusaderky) +- #478: [Linux] almost all APIs are about 30% faster on Python 3.X. +- #479: long deprecated psutil.error module is gone; exception classes now + live in "psutil" namespace only. + +**Bug fixes** + +- #193: psutil.Popen constructor can throw an exception if the spawned process + terminates quickly. +- #340: [Windows] process get_open_files() no longer hangs. (patch by + jtang@vahna.net) +- #443: [Linux] fix a potential overflow issue for Process.set_cpu_affinity() + on systems with more than 64 CPUs. +- #448: [Windows] get_children() and ppid() memory leak (patch by Ulrich + Klank). +- #457: [POSIX] pid_exists() always returns True for PID 0. +- #461: namedtuples are not pickle-able. +- #466: [Linux] process exe improper null bytes handling. (patch by + Gautam Singh) +- #470: wait_procs() might not wait. (patch by crusaderky) +- #471: [Windows] process exe improper unicode handling. (patch by + alex@mroja.net) +- #473: psutil.Popen.wait() does not set returncode attribute. +- #474: [Windows] Process.cpu_percent() is no longer capped at 100%. +- #476: [Linux] encoding error for process name and cmdline. + +**API changes** + +For the sake of consistency a lot of psutil APIs have been renamed. +In most cases accessing the old names will work but it will cause a +DeprecationWarning. + +- psutil.* module level constants have being replaced by functions: + + +-----------------------+-------------------------------+ + | Old name | Replacement | + +=======================+===============================+ + | psutil.NUM_CPUS | psutil.cpu_cpunt() | + +-----------------------+-------------------------------+ + | psutil.BOOT_TIME | psutil.boot_time() | + +-----------------------+-------------------------------+ + | psutil.TOTAL_PHYMEM | psutil.virtual_memory().total | + +-----------------------+-------------------------------+ + +- Renamed psutil.* functions: + + +--------------------------+-------------------------------+ + | Old name | Replacement | + +==========================+===============================+ + | - psutil.get_pid_list() | psutil.pids() | + +--------------------------+-------------------------------+ + | - psutil.get_users() | psutil.users() | + +--------------------------+-------------------------------+ + | - psutil.get_boot_time() | psutil.boot_time() | + +--------------------------+-------------------------------+ + +- All psutil.Process ``get_*`` methods lost the ``get_`` prefix. + get_ext_memory_info() renamed to memory_info_ex(). + Assuming "p = psutil.Process()": + + +--------------------------+----------------------+ + | Old name | Replacement | + +==========================+======================+ + | p.get_children() | p.children() | + +--------------------------+----------------------+ + | p.get_connections() | p.connections() | + +--------------------------+----------------------+ + | p.get_cpu_affinity() | p.cpu_affinity() | + +--------------------------+----------------------+ + | p.get_cpu_percent() | p.cpu_percent() | + +--------------------------+----------------------+ + | p.get_cpu_times() | p.cpu_times() | + +--------------------------+----------------------+ + | p.get_ext_memory_info() | p.memory_info_ex() | + +--------------------------+----------------------+ + | p.get_io_counters() | p.io_counters() | + +--------------------------+----------------------+ + | p.get_ionice() | p.ionice() | + +--------------------------+----------------------+ + | p.get_memory_info() | p.memory_info() | + +--------------------------+----------------------+ + | p.get_memory_maps() | p.memory_maps() | + +--------------------------+----------------------+ + | p.get_memory_percent() | p.memory_percent() | + +--------------------------+----------------------+ + | p.get_nice() | p.nice() | + +--------------------------+----------------------+ + | p.get_num_ctx_switches() | p.num_ctx_switches() | + +--------------------------+----------------------+ + | p.get_num_fds() | p.num_fds() | + +--------------------------+----------------------+ + | p.get_num_threads() | p.num_threads() | + +--------------------------+----------------------+ + | p.get_open_files() | p.open_files() | + +--------------------------+----------------------+ + | p.get_rlimit() | p.rlimit() | + +--------------------------+----------------------+ + | p.get_threads() | p.threads() | + +--------------------------+----------------------+ + | p.getcwd() | p.cwd() | + +--------------------------+----------------------+ + +- All psutil.Process ``set_*`` methods lost the ``set_`` prefix. + Assuming "p = psutil.Process()": + + +----------------------+---------------------------------+ + | Old name | Replacement | + +======================+=================================+ + | p.set_nice() | p.nice(value) | + +----------------------+---------------------------------+ + | p.set_ionice() | p.ionice(ioclass, value=None) | + +----------------------+---------------------------------+ + | p.set_cpu_affinity() | p.cpu_affinity(cpus) | + +----------------------+---------------------------------+ + | p.set_rlimit() | p.rlimit(resource, limits=None) | + +----------------------+---------------------------------+ + +- Except for 'pid' all psutil.Process class properties have been turned into + methods. This is the only case which there are no aliases. + Assuming "p = psutil.Process()": + + +---------------+-----------------+ + | Old name | Replacement | + +===============+=================+ + | p.name | p.name() | + +---------------+-----------------+ + | p.parent | p.parent() | + +---------------+-----------------+ + | p.ppid | p.ppid() | + +---------------+-----------------+ + | p.exe | p.exe() | + +---------------+-----------------+ + | p.cmdline | p.cmdline() | + +---------------+-----------------+ + | p.status | p.status() | + +---------------+-----------------+ + | p.uids | p.uids() | + +---------------+-----------------+ + | p.gids | p.gids() | + +---------------+-----------------+ + | p.username | p.username() | + +---------------+-----------------+ + | p.create_time | p.create_time() | + +---------------+-----------------+ + +- timeout parameter of cpu_percent* functions defaults to 0.0 instead of 0.1. +- long deprecated psutil.error module is gone; exception classes now live in + "psutil" namespace only. +- Process instances' "retcode" attribute returned by psutil.wait_procs() has + been renamed to "returncode" for consistency with subprocess.Popen. + + +1.2.1 - 2013-11-25 +================== + +**Bug fixes** + +- #348: [Windows XP] fixed "ImportError: DLL load failed" occurring on module + import. +- #425: [Solaris] crash on import due to failure at determining BOOT_TIME. +- #443: [Linux] can't set CPU affinity on systems with more than 64 cores. + + +1.2.0 - 2013-11-20 +================== + +**Enhancements** + +- #439: assume os.getpid() if no argument is passed to psutil.Process + constructor. +- #440: new psutil.wait_procs() utility function which waits for multiple + processes to terminate. + +**Bug fixes** + +- #348: [Windows XP/Vista] fix "ImportError: DLL load failed" occurring on + module import. + + +1.1.3 - 2013-11-07 +================== + +**Bug fixes** + +- #442: [Linux] psutil won't compile on certain version of Linux because of + missing prlimit(2) syscall. + + +1.1.2 - 2013-10-22 +================== + +**Bug fixes** + +- #442: [Linux] psutil won't compile on Debian 6.0 because of missing + prlimit(2) syscall. + + +1.1.1 - 2013-10-08 +================== + +**Bug fixes** + +- #442: [Linux] psutil won't compile on kernels < 2.6.36 due to missing + prlimit(2) syscall. + + +1.1.0 - 2013-09-28 +================== + +**Enhancements** + +- #410: host tar.gz and windows binary files are on PYPI. +- #412: [Linux] get/set process resource limits. +- #415: [Windows] Process.get_children() is an order of magnitude faster. +- #426: [Windows] Process.name is an order of magnitude faster. +- #431: [UNIX] Process.name is slightly faster because it unnecessarily + retrieved also process cmdline. + +**Bug fixes** + +- #391: [Windows] psutil.cpu_times_percent() returns negative percentages. +- #408: STATUS_* and CONN_* constants don't properly serialize on JSON. +- #411: [Windows] examples/disk_usage.py may pop-up a GUI error. +- #413: [Windows] Process.get_memory_info() leaks memory. +- #414: [Windows] Process.exe on Windows XP may raise ERROR_INVALID_PARAMETER. +- #416: psutil.disk_usage() doesn't work well with unicode path names. +- #430: [Linux] process IO counters report wrong number of r/w syscalls. +- #435: [Linux] psutil.net_io_counters() might report erreneous NIC names. +- #436: [Linux] psutil.net_io_counters() reports a wrong 'dropin' value. + +**API changes** + +- #408: turn STATUS_* and CONN_* constants into plain Python strings. + + +1.0.1 - 2013-07-12 +================== + +**Bug fixes** + +- #405: network_io_counters(pernic=True) no longer works as intended in 1.0.0. + + +1.0.0 - 2013-07-10 +================== + +**Enhancements** + +- #18: Solaris support (yay!) (thanks Justin Venus) +- #367: Process.get_connections() 'status' strings are now constants. +- #380: test suite exits with non-zero on failure. (patch by floppymaster) +- #391: introduce unittest2 facilities and provide workarounds if unittest2 + is not installed (python < 2.7). + +**Bug fixes** + +- #374: [Windows] negative memory usage reported if process uses a lot of + memory. +- #379: [Linux] Process.get_memory_maps() may raise ValueError. +- #394: [OSX] Mapped memory regions report incorrect file name. +- #404: [Linux] sched_*affinity() are implicitly declared. (patch by Arfrever) + +**API changes** + +- Process.get_connections() 'status' field is no longer a string but a + constant object (psutil.CONN_*). +- Process.get_connections() 'local_address' and 'remote_address' fields + renamed to 'laddr' and 'raddr'. +- psutil.network_io_counters() renamed to psutil.net_io_counters(). + + +0.7.1 - 2013-05-03 +================== + +**Bug fixes** + +- #325: [BSD] psutil.virtual_memory() can raise SystemError. + (patch by Jan Beich) +- #370: [BSD] Process.get_connections() requires root. (patch by John Baldwin) +- #372: [BSD] different process methods raise NoSuchProcess instead of + AccessDenied. + + +0.7.0 - 2013-04-12 +================== + +**Enhancements** + +- #233: code migrated to Mercurial (yay!) +- #246: psutil.error module is deprecated and scheduled for removal. +- #328: [Windows] process IO nice/priority support. +- #359: psutil.get_boot_time() +- #361: [Linux] psutil.cpu_times() now includes new 'steal', 'guest' and + 'guest_nice' fields available on recent Linux kernels. + Also, psutil.cpu_percent() is more accurate. +- #362: cpu_times_percent() (per-CPU-time utilization as a percentage) + +**Bug fixes** + +- #234: [Windows] disk_io_counters() fails to list certain disks. +- #264: [Windows] use of psutil.disk_partitions() may cause a message box to + appear. +- #313: [Linux] psutil.virtual_memory() and psutil.swap_memory() can crash on + certain exotic Linux flavors having an incomplete /proc interface. + If that's the case we now set the unretrievable stats to 0 and raise a + RuntimeWarning. +- #315: [OSX] fix some compilation warnings. +- #317: [Windows] cannot set process CPU affinity above 31 cores. +- #319: [Linux] process get_memory_maps() raises KeyError 'Anonymous' on Debian + squeeze. +- #321: [UNIX] Process.ppid property is no longer cached as the kernel may set + the ppid to 1 in case of a zombie process. +- #323: [OSX] disk_io_counters()'s read_time and write_time parameters were + reporting microseconds not milliseconds. (patch by Gregory Szorc) +- #331: Process cmdline is no longer cached after first acces as it may change. +- #333: [OSX] Leak of Mach ports on OS X (patch by rsesek@google.com) +- #337: [Linux] process methods not working because of a poor /proc + implementation will raise NotImplementedError rather than RuntimeError + and Process.as_dict() will not blow up. (patch by Curtin1060) +- #338: [Linux] disk_io_counters() fails to find some disks. +- #339: [FreeBSD] get_pid_list() can allocate all the memory on system. +- #341: [Linux] psutil might crash on import due to error in retrieving system + terminals map. +- #344: [FreeBSD] swap_memory() might return incorrect results due to + kvm_open(3) not being called. (patch by Jean Sebastien) +- #338: [Linux] disk_io_counters() fails to find some disks. +- #351: [Windows] if psutil is compiled with mingw32 (provided installers for + py2.4 and py2.5 are) disk_io_counters() will fail. (Patch by m.malycha) +- #353: [OSX] get_users() returns an empty list on OSX 10.8. +- #356: Process.parent now checks whether parent PID has been reused in which + case returns None. +- #365: Process.set_nice() should check PID has not been reused by another + process. +- #366: [FreeBSD] get_memory_maps(), get_num_fds(), get_open_files() and + getcwd() Process methods raise RuntimeError instead of AccessDenied. + +**API changes** + +- Process.cmdline property is no longer cached after first access. +- Process.ppid property is no longer cached after first access. +- [Linux] Process methods not working because of a poor /proc implementation + will raise NotImplementedError instead of RuntimeError. +- psutil.error module is deprecated and scheduled for removal. + + +0.6.1 - 2012-08-16 +================== + +**Enhancements** + +- #316: process cmdline property now makes a better job at guessing the process + executable from the cmdline. + +**Bug fixes** + +- #316: process exe was resolved in case it was a symlink. +- #318: python 2.4 compatibility was broken. + +**API changes** + +- process exe can now return an empty string instead of raising AccessDenied. +- process exe is no longer resolved in case it's a symlink. + + +0.6.0 - 2012-08-13 +================== + +**Enhancements** + +- #216: [POSIX] get_connections() UNIX sockets support. +- #220: [FreeBSD] get_connections() has been rewritten in C and no longer + requires lsof. +- #222: [OSX] add support for process cwd. +- #261: process extended memory info. +- #295: [OSX] process executable path is now determined by asking the OS + instead of being guessed from process cmdline. +- #297: [OSX] the Process methods below were always raising AccessDenied for + any process except the current one. Now this is no longer true. Also + they are 2.5x faster. + - name + - get_memory_info() + - get_memory_percent() + - get_cpu_times() + - get_cpu_percent() + - get_num_threads() +- #300: examples/pmap.py script. +- #301: process_iter() now yields processes sorted by their PIDs. +- #302: process number of voluntary and involuntary context switches. +- #303: [Windows] the Process methods below were always raising AccessDenied + for any process not owned by current user. Now this is no longer true: + - create_time + - get_cpu_times() + - get_cpu_percent() + - get_memory_info() + - get_memory_percent() + - get_num_handles() + - get_io_counters() +- #305: add examples/netstat.py script. +- #311: system memory functions has been refactorized and rewritten and now + provide a more detailed and consistent representation of the system + memory. New psutil.virtual_memory() function provides the following + memory amounts: + - total + - available + - percent + - used + - active [POSIX] + - inactive [POSIX] + - buffers (BSD, Linux) + - cached (BSD, OSX) + - wired (OSX, BSD) + - shared [FreeBSD] + New psutil.swap_memory() provides: + - total + - used + - free + - percent + - sin (no. of bytes the system has swapped in from disk (cumulative)) + - sout (no. of bytes the system has swapped out from disk (cumulative)) + All old memory-related functions are deprecated. + Also two new example scripts were added: free.py and meminfo.py. +- #312: psutil.network_io_counters() namedtuple includes 4 new fields: + errin, errout dropin and dropout, reflecting the number of packets + dropped and with errors. + +**Bugfixes** + +- #298: [OSX and BSD] memory leak in get_num_fds(). +- #299: potential memory leak every time PyList_New(0) is used. +- #303: [Windows] potential heap corruption in get_num_threads() and + get_status() Process methods. +- #305: [FreeBSD] psutil can't compile on FreeBSD 9 due to removal of utmp.h. +- #306: at C level, errors are not checked when invoking Py* functions which + create or manipulate Python objects leading to potential memory related + errors and/or segmentation faults. +- #307: [FreeBSD] values returned by psutil.network_io_counters() are wrong. +- #308: [BSD / Windows] psutil.virtmem_usage() wasn't actually returning + information about swap memory usage as it was supposed to do. It does + now. +- #309: get_open_files() might not return files which can not be accessed + due to limited permissions. AccessDenied is now raised instead. + +**API changes** + +- psutil.phymem_usage() is deprecated (use psutil.virtual_memory()) +- psutil.virtmem_usage() is deprecated (use psutil.swap_memory()) +- psutil.phymem_buffers() on Linux is deprecated (use psutil.virtual_memory()) +- psutil.cached_phymem() on Linux is deprecated (use psutil.virtual_memory()) +- [Windows and BSD] psutil.virtmem_usage() now returns information about swap + memory instead of virtual memory. + + +0.5.1 - 2012-06-29 +================== + +**Enhancements** + +- #293: [Windows] process executable path is now determined by asking the OS + instead of being guessed from process cmdline. + +**Bugfixes** + +- #292: [Linux] race condition in process files/threads/connections. +- #294: [Windows] Process CPU affinity is only able to set CPU #0. + + +0.5.0 - 2012-06-27 +================== + +**Enhancements** + +- #195: [Windows] number of handles opened by process. +- #209: psutil.disk_partitions() now provides also mount options. +- #229: list users currently connected on the system (psutil.get_users()). +- #238: [Linux, Windows] process CPU affinity (get and set). +- #242: Process.get_children(recursive=True): return all process + descendants. +- #245: [POSIX] Process.wait() incrementally consumes less CPU cycles. +- #257: [Windows] removed Windows 2000 support. +- #258: [Linux] Process.get_memory_info() is now 0.5x faster. +- #260: process's mapped memory regions. (Windows patch by wj32.64, OSX patch + by Jeremy Whitlock) +- #262: [Windows] psutil.disk_partitions() was slow due to inspecting the + floppy disk drive also when "all" argument was False. +- #273: psutil.get_process_list() is deprecated. +- #274: psutil no longer requires 2to3 at installation time in order to work + with Python 3. +- #278: new Process.as_dict() method. +- #281: ppid, name, exe, cmdline and create_time properties of Process class + are now cached after being accessed. +- #282: psutil.STATUS_* constants can now be compared by using their string + representation. +- #283: speedup Process.is_running() by caching its return value in case the + process is terminated. +- #284: [POSIX] per-process number of opened file descriptors. +- #287: psutil.process_iter() now caches Process instances between calls. +- #290: Process.nice property is deprecated in favor of new get_nice() and + set_nice() methods. + +**Bugfixes** + +- #193: psutil.Popen constructor can throw an exception if the spawned process + terminates quickly. +- #240: [OSX] incorrect use of free() for Process.get_connections(). +- #244: [POSIX] Process.wait() can hog CPU resources if called against a + process which is not our children. +- #248: [Linux] psutil.network_io_counters() might return erroneous NIC names. +- #252: [Windows] process getcwd() erroneously raise NoSuchProcess for + processes owned by another user. It now raises AccessDenied instead. +- #266: [Windows] psutil.get_pid_list() only shows 1024 processes. + (patch by Amoser) +- #267: [OSX] Process.get_connections() - an erroneous remote address was + returned. (Patch by Amoser) +- #272: [Linux] Porcess.get_open_files() - potential race condition can lead to + unexpected NoSuchProcess exception. Also, we can get incorrect reports + of not absolutized path names. +- #275: [Linux] Process.get_io_counters() erroneously raise NoSuchProcess on + old Linux versions. Where not available it now raises + NotImplementedError. +- #286: Process.is_running() doesn't actually check whether PID has been + reused. +- #314: Process.get_children() can sometimes return non-children. + +**API changes** + +- Process.nice property is deprecated in favor of new get_nice() and set_nice() + methods. +- psutil.get_process_list() is deprecated. +- ppid, name, exe, cmdline and create_time properties of Process class are now + cached after being accessed, meaning NoSuchProcess will no longer be raised + in case the process is gone in the meantime. +- psutil.STATUS_* constants can now be compared by using their string + representation. + + +0.4.1 - 2011-12-14 +================== + +**Bugfixes** + +- #228: some example scripts were not working with python 3. +- #230: [Windows / OSX] memory leak in Process.get_connections(). +- #232: [Linux] psutil.phymem_usage() can report erroneous values which are + different than "free" command. +- #236: [Windows] memory/handle leak in Process's get_memory_info(), + suspend() and resume() methods. + + +0.4.0 - 2011-10-29 +================== + +**Enhancements** + +- #150: network I/O counters. (OSX and Windows patch by Jeremy Whitlock) +- #154: [FreeBSD] add support for process getcwd() +- #157: [Windows] provide installer for Python 3.2 64-bit. +- #198: Process.wait(timeout=0) can now be used to make wait() return + immediately. +- #206: disk I/O counters. (OSX and Windows patch by Jeremy Whitlock) +- #213: examples/iotop.py script. +- #217: Process.get_connections() now has a "kind" argument to filter + for connections with different criteria. +- #221: [FreeBSD] Process.get_open_files has been rewritten in C and no longer + relies on lsof. +- #223: examples/top.py script. +- #227: examples/nettop.py script. + +**Bugfixes** + +- #135: [OSX] psutil cannot create Process object. +- #144: [Linux] no longer support 0 special PID. +- #188: [Linux] psutil import error on Linux ARM architectures. +- #194: [POSIX] psutil.Process.get_cpu_percent() now reports a percentage over + 100 on multicore processors. +- #197: [Linux] Process.get_connections() is broken on platforms not + supporting IPv6. +- #200: [Linux] psutil.NUM_CPUS not working on armel and sparc architectures + and causing crash on module import. +- #201: [Linux] Process.get_connections() is broken on big-endian + architectures. +- #211: Process instance can unexpectedly raise NoSuchProcess if tested for + equality with another object. +- #218: [Linux] crash at import time on Debian 64-bit because of a missing + line in /proc/meminfo. +- #226: [FreeBSD] crash at import time on FreeBSD 7 and minor. + + +0.3.0 - 2011-07-08 +================== + +**Enhancements** + +- #125: system per-cpu percentage utilization and times. +- #163: per-process associated terminal (TTY). +- #171: added get_phymem() and get_virtmem() functions returning system + memory information (total, used, free) and memory percent usage. + total_* avail_* and used_* memory functions are deprecated. +- #172: disk usage statistics. +- #174: mounted disk partitions. +- #179: setuptools is now used in setup.py + +**Bugfixes** + +- #159: SetSeDebug() does not close handles or unset impersonation on return. +- #164: [Windows] wait function raises a TimeoutException when a process + returns -1 . +- #165: process.status raises an unhandled exception. +- #166: get_memory_info() leaks handles hogging system resources. +- #168: psutil.cpu_percent() returns erroneous results when used in + non-blocking mode. (patch by Philip Roberts) +- #178: OSX - Process.get_threads() leaks memory +- #180: [Windows] Process's get_num_threads() and get_threads() methods can + raise NoSuchProcess exception while process still exists. + + +0.2.1 - 2011-03-20 +================== + +**Enhancements** + +- #64: per-process I/O counters. +- #116: per-process wait() (wait for process to terminate and return its exit + code). +- #134: per-process get_threads() returning information (id, user and kernel + times) about threads opened by process. +- #136: process executable path on FreeBSD is now determined by asking the + kernel instead of guessing it from cmdline[0]. +- #137: per-process real, effective and saved user and group ids. +- #140: system boot time. +- #142: per-process get and set niceness (priority). +- #143: per-process status. +- #147: per-process I/O nice (priority) - Linux only. +- #148: psutil.Popen class which tidies up subprocess.Popen and psutil.Process + in a unique interface. +- #152: [OSX] get_process_open_files() implementation has been rewritten + in C and no longer relies on lsof resulting in a 3x speedup. +- #153: [OSX] get_process_connection() implementation has been rewritten + in C and no longer relies on lsof resulting in a 3x speedup. + +**Bugfixes** + +- #83: process cmdline is empty on OSX 64-bit. +- #130: a race condition can cause IOError exception be raised on + Linux if process disappears between open() and subsequent read() calls. +- #145: WindowsError was raised instead of psutil.AccessDenied when using + process resume() or suspend() on Windows. +- #146: 'exe' property on Linux can raise TypeError if path contains NULL + bytes. +- #151: exe and getcwd() for PID 0 on Linux return inconsistent data. + +**API changes** + +- Process "uid" and "gid" properties are deprecated in favor of "uids" and + "gids" properties. + + +0.2.0 - 2010-11-13 +================== + +**Enhancements** + +- #79: per-process open files. +- #88: total system physical cached memory. +- #88: total system physical memory buffers used by the kernel. +- #91: per-process send_signal() and terminate() methods. +- #95: NoSuchProcess and AccessDenied exception classes now provide "pid", + "name" and "msg" attributes. +- #97: per-process children. +- #98: Process.get_cpu_times() and Process.get_memory_info now return + a namedtuple instead of a tuple. +- #103: per-process opened TCP and UDP connections. +- #107: add support for Windows 64 bit. (patch by cjgohlke) +- #111: per-process executable name. +- #113: exception messages now include process name and pid. +- #114: process username Windows implementation has been rewritten in pure + C and no longer uses WMI resulting in a big speedup. Also, pywin32 is no + longer required as a third-party dependancy. (patch by wj32) +- #117: added support for Windows 2000. +- #123: psutil.cpu_percent() and psutil.Process.cpu_percent() accept a + new 'interval' parameter. +- #129: per-process number of threads. + +**Bugfixes** + +- #80: fixed warnings when installing psutil with easy_install. +- #81: psutil fails to compile with Visual Studio. +- #94: suspend() raises OSError instead of AccessDenied. +- #86: psutil didn't compile against FreeBSD 6.x. +- #102: orphaned process handles obtained by using OpenProcess in C were + left behind every time Process class was instantiated. +- #111: path and name Process properties report truncated or erroneous + values on UNIX. +- #120: cpu_percent() always returning 100% on OS X. +- #112: uid and gid properties don't change if process changes effective + user/group id at some point. +- #126: ppid, uid, gid, name, exe, cmdline and create_time properties are + no longer cached and correctly raise NoSuchProcess exception if the process + disappears. + +**API changes** + +- psutil.Process.path property is deprecated and works as an alias for "exe" + property. +- psutil.Process.kill(): signal argument was removed - to send a signal to the + process use send_signal(signal) method instead. +- psutil.Process.get_memory_info() returns a nametuple instead of a tuple. +- psutil.cpu_times() returns a nametuple instead of a tuple. +- New psutil.Process methods: get_open_files(), get_connections(), + send_signal() and terminate(). +- ppid, uid, gid, name, exe, cmdline and create_time properties are no longer + cached and raise NoSuchProcess exception if process disappears. +- psutil.cpu_percent() no longer returns immediately (see issue 123). +- psutil.Process.get_cpu_percent() and psutil.cpu_percent() no longer returns + immediately by default (see issue 123). + + +0.1.3 - 2010-03-02 +================== + +**Enhancements** + +- #14: per-process username +- #51: per-process current working directory (Windows and Linux only) +- #59: Process.is_running() is now 10 times faster +- #61: added supoprt for FreeBSD 64 bit +- #71: implemented suspend/resume process +- #75: python 3 support + +**Bugfixes** + +- #36: process cpu_times() and memory_info() functions succeeded also for dead + processes while a NoSuchProcess exception is supposed to be raised. +- #48: incorrect size for mib array defined in getcmdargs for BSD +- #49: possible memory leak due to missing free() on error condition on +- #50: fixed getcmdargs() memory fragmentation on BSD +- #55: test_pid_4 was failing on Windows Vista +- #57: some unit tests were failing on systems where no swap memory is + available +- #58: is_running() is now called before kill() to make sure we are going + to kill the correct process. +- #73: virtual memory size reported on OS X includes shared library size +- #77: NoSuchProcess wasn't raised on Process.create_time if kill() was + used first. + + +0.1.2 - 2009-05-06 +================== + +**Enhancements** + +- #32: Per-process CPU user/kernel times +- #33: Process create time +- #34: Per-process CPU utilization percentage +- #38: Per-process memory usage (bytes) +- #41: Per-process memory utilization (percent) +- #39: System uptime +- #43: Total system virtual memory +- #46: Total system physical memory +- #44: Total system used/free virtual and physical memory + +**Bugfixes** + +- #36: [Windows] NoSuchProcess not raised when accessing timing methods. +- #40: test_get_cpu_times() failing on FreeBSD and OS X. +- #42: [Windows] get_memory_percent() raises AccessDenied. + + +0.1.1 - 2009-03-06 +================== + +**Enhancements** + +- #4: FreeBSD support for all functions of psutil +- #9: Process.uid and Process.gid now retrieve process UID and GID. +- #11: Support for parent/ppid - Process.parent property returns a + Process object representing the parent process, and Process.ppid returns + the parent PID. +- #12 & 15: + NoSuchProcess exception now raised when creating an object + for a nonexistent process, or when retrieving information about a process + that has gone away. +- #21: AccessDenied exception created for raising access denied errors + from OSError or WindowsError on individual platforms. +- #26: psutil.process_iter() function to iterate over processes as + Process objects with a generator. +- #?: Process objects can now also be compared with == operator for equality + (PID, name, command line are compared). + +**Bugfixes** + +- #16: [Windows] Special case for "System Idle Process" (PID 0) which + otherwise would return an "invalid parameter" exception. +- #17: get_process_list() ignores NoSuchProcess and AccessDenied + exceptions during building of the list. +- #22: [Windows] Process(0).kill() was failing with an unset exception. +- #23: Special case for pid_exists(0) +- #24: [Windows] Process(0).kill() now raises AccessDenied exception instead + of WindowsError. +- #30: psutil.get_pid_list() was returning two instances of PID 0 on OSX and + FreeBSD platforms. + + +0.1.0 - 2009-01-27 +================== + +- Initial release. diff --git a/python/psutil/INSTALL.rst b/python/psutil/INSTALL.rst new file mode 100644 index 0000000000..e518c430ee --- /dev/null +++ b/python/psutil/INSTALL.rst @@ -0,0 +1,116 @@ +============================ +Installing using pip on UNIX +============================ + +The easiest way to install psutil on UNIX is by using pip (but first you might +need to install python header files; see later). +First install pip:: + + $ wget https://bootstrap.pypa.io/get-pip.py + $ python get-pip.py + +...then run:: + + $ pip install psutil + +You may need to install gcc and python header files first (see later). + + +===================== +Installing on Windows +===================== + +Just get the right installer for your Python version and architecture from: +https://pypi.python.org/pypi/psutil/#downloads +Since wheels installers are also available you may also use pip. + + +======================================== +Compiling on Windows using Visual Studio +======================================== + +In order to compile psutil on Windows you'll need Visual Studio (Mingw32 is +no longer supported). You must have the same version of Visual Studio used to compile +your installation of Python, that is:: + +* Python 2.6: VS 2008 +* Python 2.7: VS 2008 +* Python 3.3, 3.4: VS 2010 (you can download it from `MS website <http://www.visualstudio.com/downloads/download-visual-studio-vs#d-2010-express>`_) +* Python 3.5: `VS 2015 UP <http://www.visualstudio.com/en-au/news/vs2015-preview-vs>`_ + +...then run:: + + setup.py build + +...or:: + + make.bat build + +Compiling 64 bit versions of Python 2.6 and 2.7 with VS 2008 requires +Windows SDK and .NET Framework 3.5 SP1 to be installed first. +Once you have those run vcvars64.bat, then compile: +http://stackoverflow.com/questions/11072521/ + +=================== +Installing on Linux +=================== + +gcc is required and so the python headers. They can easily be installed by +using the distro package manager. For example, on Debian and Ubuntu:: + + $ sudo apt-get install gcc python-dev + +...on Redhat and CentOS:: + + $ sudo yum install gcc python-devel + +Once done, you can build/install psutil with:: + + $ python setup.py install + + +================== +Installing on OS X +================== + +OS X installation from source will require gcc which you can obtain as part of +the 'XcodeTools' installer from Apple. Then you can run the standard distutils +commands. +To build only:: + + $ python setup.py build + +To install and build:: + + $ python setup.py install + + +===================== +Installing on FreeBSD +===================== + +The same compiler used to install Python must be present on the system in order +to build modules using distutils. Assuming it is installed, you can build using +the standard distutils commands. + +Build only:: + + $ python setup.py build + +Install and build:: + + $ python setup.py install + + +======== +Makefile +======== + +A makefile is available for both UNIX and Windows (make.bat). It provides +some automations for the tasks described above and might be preferred over +using setup.py. With it you can:: + + $ make install # just install (in --user mode) + $ make uninstall # uninstall (needs pip) + $ make test # run tests + $ make clean # remove installation files diff --git a/python/psutil/LICENSE b/python/psutil/LICENSE new file mode 100644 index 0000000000..e91b1359a2 --- /dev/null +++ b/python/psutil/LICENSE @@ -0,0 +1,27 @@ +psutil is distributed under BSD license reproduced below. + +Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola' +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the psutil authors nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/python/psutil/MANIFEST.in b/python/psutil/MANIFEST.in new file mode 100644 index 0000000000..d807be289d --- /dev/null +++ b/python/psutil/MANIFEST.in @@ -0,0 +1,22 @@ +include .coveragerc +include .git-pre-commit +include .git-pre-commit +include .gitignore +include .travis.yml +include CREDITS +include HISTORY.rst +include INSTALL.rst +include LICENSE +include make.bat +include Makefile +include MANIFEST.in +include README.rst +include setup.py +include TODO +include tox.ini +recursive-exclude docs/_build * +recursive-include .appveyor/* +recursive-include docs * +recursive-include examples *.py +recursive-include psutil *.py *.c *.h +recursive-include test *.py README* diff --git a/python/psutil/Makefile b/python/psutil/Makefile new file mode 100644 index 0000000000..1e4eb4b01a --- /dev/null +++ b/python/psutil/Makefile @@ -0,0 +1,122 @@ +# Shortcuts for various tasks (UNIX only). +# To use a specific Python version run: +# $ make install PYTHON=python3.3 + +# You can set these variables from the command line. +PYTHON = python +TSCRIPT = test/test_psutil.py + +all: test + +clean: + rm -f `find . -type f -name \*.py[co]` + rm -f `find . -type f -name \*.so` + rm -f `find . -type f -name .\*~` + rm -f `find . -type f -name \*.orig` + rm -f `find . -type f -name \*.bak` + rm -f `find . -type f -name \*.rej` + rm -rf `find . -type d -name __pycache__` + rm -rf *.core + rm -rf *.egg-info + rm -rf *\$testfile* + rm -rf .coverage + rm -rf .tox + rm -rf build + rm -rf dist + rm -rf docs/_build + rm -rf htmlcov + +build: clean + $(PYTHON) setup.py build + @# copies *.so files in ./psutil directory in order to allow + @# "import psutil" when using the interactive interpreter from within + @# this directory. + $(PYTHON) setup.py build_ext -i + +# useful deps which are nice to have while developing / testing +setup-dev-env: + python -c "import urllib2; \ + r = urllib2.urlopen('https://bootstrap.pypa.io/get-pip.py'); \ + open('/tmp/get-pip.py', 'w').write(r.read());" + $(PYTHON) /tmp/get-pip.py --user + rm /tmp/get-pip.py + $(PYTHON) -m pip install --user --upgrade pip + $(PYTHON) -m pip install --user --upgrade \ + coverage \ + flake8 \ + ipaddress \ + ipdb \ + mock==1.0.1 \ + nose \ + pep8 \ + pyflakes \ + sphinx \ + sphinx-pypi-upload \ + unittest2 \ + +install: build + $(PYTHON) setup.py install --user + +uninstall: + cd ..; $(PYTHON) -m pip uninstall -y -v psutil + +test: install + $(PYTHON) $(TSCRIPT) + +test-process: install + $(PYTHON) -m unittest -v test.test_psutil.TestProcess + +test-system: install + $(PYTHON) -m unittest -v test.test_psutil.TestSystemAPIs + +test-memleaks: install + $(PYTHON) test/test_memory_leaks.py + +# Run a specific test by name; e.g. "make test-by-name disk_" will run +# all test methods containing "disk_" in their name. +# Requires "pip install nose". +test-by-name: install + @$(PYTHON) -m nose test/test_psutil.py test/_* --nocapture -v -m $(filter-out $@,$(MAKECMDGOALS)) + +# Same as above but for test_memory_leaks.py script. +test-memleaks-by-name: install + @$(PYTHON) -m nose test/test_memory_leaks.py --nocapture -v -m $(filter-out $@,$(MAKECMDGOALS)) + +coverage: install + # Note: coverage options are controlled by .coveragerc file + rm -rf .coverage htmlcov + $(PYTHON) -m coverage run $(TSCRIPT) + $(PYTHON) -m coverage report + @echo "writing results to htmlcov/index.html" + $(PYTHON) -m coverage html + $(PYTHON) -m webbrowser -t htmlcov/index.html + +pep8: + @git ls-files | grep \\.py$ | xargs $(PYTHON) -m pep8 + +pyflakes: + @export PYFLAKES_NODOCTEST=1 && \ + git ls-files | grep \\.py$ | xargs $(PYTHON) -m pyflakes + +flake8: + @git ls-files | grep \\.py$ | xargs $(PYTHON) -m flake8 + +# Upload source tarball on https://pypi.python.org/pypi/psutil. +upload-src: clean + $(PYTHON) setup.py sdist upload + +# Build and upload doc on https://pythonhosted.org/psutil/. +# Requires "pip install sphinx-pypi-upload". +upload-doc: + cd docs; make html + $(PYTHON) setup.py upload_sphinx --upload-dir=docs/_build/html + +# git-tag a new release +git-tag-release: + git tag -a release-`python -c "import setup; print(setup.get_version())"` -m `git rev-list HEAD --count`:`git rev-parse --short HEAD` + echo "done; now run 'git push --follow-tags' to push the new tag on the remote repo" + +# install GIT pre-commit hook +install-git-hooks: + ln -sf ../../.git-pre-commit .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit diff --git a/python/psutil/PKG-INFO b/python/psutil/PKG-INFO new file mode 100644 index 0000000000..e74d33f658 --- /dev/null +++ b/python/psutil/PKG-INFO @@ -0,0 +1,434 @@ +Metadata-Version: 1.1 +Name: psutil +Version: 3.1.1 +Summary: psutil is a cross-platform library for retrieving information onrunning processes and system utilization (CPU, memory, disks, network)in Python. +Home-page: https://github.com/giampaolo/psutil +Author: Giampaolo Rodola +Author-email: g.rodola <at> gmail <dot> com +License: BSD +Description: .. image:: https://img.shields.io/pypi/dm/psutil.svg + :target: https://pypi.python.org/pypi/psutil#downloads + :alt: Downloads this month + + .. image:: https://api.travis-ci.org/giampaolo/psutil.png?branch=master + :target: https://travis-ci.org/giampaolo/psutil + :alt: Linux tests (Travis) + + .. image:: https://ci.appveyor.com/api/projects/status/qdwvw7v1t915ywr5/branch/master?svg=true + :target: https://ci.appveyor.com/project/giampaolo/psutil + :alt: Windows tests (Appveyor) + + .. image:: https://coveralls.io/repos/giampaolo/psutil/badge.svg?branch=master&service=github + :target: https://coveralls.io/github/giampaolo/psutil?branch=master + :alt: Test coverage (coverall.io) + + .. image:: https://img.shields.io/pypi/v/psutil.svg + :target: https://pypi.python.org/pypi/psutil/ + :alt: Latest version + + .. image:: https://img.shields.io/github/stars/giampaolo/psutil.svg + :target: https://github.com/giampaolo/psutil/ + :alt: Github stars + + .. image:: https://img.shields.io/scrutinizer/g/giampaolo/psutil.svg + :target: https://scrutinizer-ci.com/g/giampaolo/psutil/ + :alt: Code quality (scrutinizer-ci.com) + + .. image:: https://img.shields.io/pypi/l/psutil.svg + :target: https://pypi.python.org/pypi/psutil/ + :alt: License + + =========== + Quick links + =========== + + - `Home page <https://github.com/giampaolo/psutil>`_ + - `Documentation <http://pythonhosted.org/psutil/>`_ + - `Installation <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_ + - `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`_ + - `Forum <http://groups.google.com/group/psutil/topics>`_ + - `Blog <http://grodola.blogspot.com/search/label/psutil>`_ + - `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_ + - `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_ + + ======= + Summary + ======= + + psutil (python system and process utilities) is a cross-platform library for + retrieving information on **running processes** and **system utilization** + (CPU, memory, disks, network) in Python. It is useful mainly for **system + monitoring**, **profiling and limiting process resources** and **management of + running processes**. It implements many functionalities offered by command line + tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, + ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It currently supports + **Linux, Windows, OSX, FreeBSD** and **Sun Solaris**, both **32-bit** and + **64-bit** architectures, with Python versions from **2.6 to 3.5** (users of + Python 2.4 and 2.5 may use `2.1.3 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__ version). + `PyPy <http://pypy.org/>`__ is also known to work. + + ==================== + Example applications + ==================== + + .. image:: http://psutil.googlecode.com/svn/wiki/images/top-thumb.png + :target: http://psutil.googlecode.com/svn/wiki/images/top.png + :alt: top + + .. image:: http://psutil.googlecode.com/svn/wiki/images/nettop-thumb.png + :target: http://psutil.googlecode.com/svn/wiki/images/nettop.png + :alt: nettop + + .. image:: http://psutil.googlecode.com/svn/wiki/images/iotop-thumb.png + :target: http://psutil.googlecode.com/svn/wiki/images/iotop.png + :alt: iotop + + See also: + + * https://github.com/nicolargo/glances + * https://github.com/google/grr + * https://github.com/Jahaja/psdash + + ============== + Example usages + ============== + + CPU + === + + .. code-block:: python + + >>> 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) + >>> + >>> for x in range(3): + ... psutil.cpu_percent(interval=1) + ... + 4.0 + 5.9 + 3.8 + >>> + >>> for x in range(3): + ... psutil.cpu_percent(interval=1, percpu=True) + ... + [4.0, 6.9, 3.7, 9.2] + [7.0, 8.5, 2.4, 2.1] + [1.2, 9.0, 9.9, 7.2] + >>> + >>> + >>> for x in range(3): + ... psutil.cpu_times_percent(interval=1, percpu=False) + ... + scputimes(user=1.5, nice=0.0, system=0.5, idle=96.5, iowait=1.5, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + scputimes(user=1.0, nice=0.0, system=0.0, idle=99.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + scputimes(user=2.0, nice=0.0, system=0.0, idle=98.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + >>> + >>> psutil.cpu_count() + 4 + >>> psutil.cpu_count(logical=False) + 2 + >>> + + Memory + ====== + + .. code-block:: python + + >>> psutil.virtual_memory() + svmem(total=8374149120, available=2081050624, percent=75.1, used=8074080256, free=300068864, active=3294920704, inactive=1361616896, buffers=529895424, cached=1251086336) + >>> psutil.swap_memory() + sswap(total=2097147904, used=296128512, free=1801019392, percent=14.1, sin=304193536, sout=677842944) + >>> + + Disks + ===== + + .. code-block:: python + + >>> psutil.disk_partitions() + [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'), + sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')] + >>> + >>> psutil.disk_usage('/') + sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5) + >>> + >>> psutil.disk_io_counters(perdisk=False) + sdiskio(read_count=719566, write_count=1082197, read_bytes=18626220032, write_bytes=24081764352, read_time=5023392, write_time=63199568) + >>> + + Network + ======= + + .. code-block:: python + + >>> psutil.net_io_counters(pernic=True) + {'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0), + 'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)} + >>> + >>> psutil.net_connections() + [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED', pid=1254), + pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING', pid=2987), + pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED', pid=None), + pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT', pid=None) + ...] + >>> + >>> psutil.net_if_addrs() + {'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1'), + snic(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None), + snic(family=<AddressFamily.AF_LINK: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00')], + 'wlan0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255'), + snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None), + snic(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff')]} + >>> + >>> psutil.net_if_stats() + {'eth0': 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)} + + Other system info + ================= + + .. code-block:: python + + >>> psutil.users() + [user(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0), + user(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0)] + >>> + >>> psutil.boot_time() + 1365519115.0 + >>> + + Process management + ================== + + .. code-block:: python + + >>> import psutil + >>> psutil.pids() + [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, + 268, 1215, 1216, 1220, 1221, 1243, 1244, 1301, 1601, 2237, 2355, + 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, + 4263, 4282, 4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, + 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446, 5167, 5234, 5235, + 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071] + >>> + >>> p = psutil.Process(7055) + >>> p.name() + 'python' + >>> p.exe() + '/usr/bin/python' + >>> p.cwd() + '/home/giampaolo' + >>> p.cmdline() + ['/usr/bin/python', 'main.py'] + >>> + >>> p.status() + 'running' + >>> p.username() + 'giampaolo' + >>> p.create_time() + 1267551141.5019531 + >>> p.terminal() + '/dev/pts/0' + >>> + >>> p.uids() + puids(real=1000, effective=1000, saved=1000) + >>> p.gids() + pgids(real=1000, effective=1000, saved=1000) + >>> + >>> p.cpu_times() + pcputimes(user=1.02, system=0.31) + >>> p.cpu_percent(interval=1.0) + 12.1 + >>> p.cpu_affinity() + [0, 1, 2, 3] + >>> p.cpu_affinity([0]) # set + >>> + >>> p.memory_percent() + 0.63423 + >>> + >>> p.memory_info() + pmem(rss=7471104, vms=68513792) + >>> p.memory_info_ex() + extmem(rss=9662464, vms=49192960, shared=3612672, text=2564096, lib=0, data=5754880, dirty=0) + >>> p.memory_maps() + [pmmap_grouped(path='/lib/x86_64-linux-gnu/libutil-2.15.so', rss=16384, anonymous=8192, swap=0), + pmmap_grouped(path='/lib/x86_64-linux-gnu/libc-2.15.so', rss=6384, anonymous=15, swap=0), + pmmap_grouped(path='/lib/x86_64-linux-gnu/libcrypto.so.1.0.0', rss=34124, anonymous=1245, swap=0), + pmmap_grouped(path='[heap]', rss=54653, anonymous=8192, swap=0), + pmmap_grouped(path='[stack]', rss=1542, anonymous=166, swap=0), + ...] + >>> + >>> p.io_counters() + pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632) + >>> + >>> p.open_files() + [popenfile(path='/home/giampaolo/svn/psutil/somefile', fd=3)] + >>> + >>> p.connections() + [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED'), + pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING'), + pconn(fd=119, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED'), + pconn(fd=123, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT')] + >>> + >>> p.num_threads() + 4 + >>> p.num_fds() + 8 + >>> p.threads() + [pthread(id=5234, user_time=22.5, system_time=9.2891), + pthread(id=5235, user_time=0.0, system_time=0.0), + pthread(id=5236, user_time=0.0, system_time=0.0), + pthread(id=5237, user_time=0.0707, system_time=1.1)] + >>> + >>> p.num_ctx_switches() + pctxsw(voluntary=78, involuntary=19) + >>> + >>> p.nice() + 0 + >>> p.nice(10) # set + >>> + >>> p.ionice(psutil.IOPRIO_CLASS_IDLE) # IO priority (Win and Linux only) + >>> p.ionice() + pionice(ioclass=<IOPriority.IOPRIO_CLASS_IDLE: 3>, value=0) + >>> + >>> p.rlimit(psutil.RLIMIT_NOFILE, (5, 5)) # set resource limits (Linux only) + >>> p.rlimit(psutil.RLIMIT_NOFILE) + (5, 5) + >>> + >>> p.suspend() + >>> p.resume() + >>> + >>> p.terminate() + >>> p.wait(timeout=3) + 0 + >>> + >>> psutil.test() + USER PID %CPU %MEM VSZ RSS TTY START TIME COMMAND + root 1 0.0 0.0 24584 2240 Jun17 00:00 init + root 2 0.0 0.0 0 0 Jun17 00:00 kthreadd + root 3 0.0 0.0 0 0 Jun17 00:05 ksoftirqd/0 + ... + giampaolo 31475 0.0 0.0 20760 3024 /dev/pts/0 Jun19 00:00 python2.4 + giampaolo 31721 0.0 2.2 773060 181896 00:04 10:30 chrome + root 31763 0.0 0.0 0 0 00:05 00:00 kworker/0:1 + >>> + + Further process APIs + ==================== + + .. code-block:: python + + >>> for p in psutil.process_iter(): + ... print(p) + ... + psutil.Process(pid=1, name='init') + psutil.Process(pid=2, name='kthreadd') + psutil.Process(pid=3, name='ksoftirqd/0') + ... + >>> + >>> def on_terminate(proc): + ... print("process {} terminated".format(proc)) + ... + >>> # waits for multiple processes to terminate + >>> gone, alive = psutil.wait_procs(procs_list, 3, callback=on_terminate) + >>> + + ====== + Donate + ====== + + A lot of time and effort went into making psutil as it is right now. + If you feel psutil is useful to you or your business and want to support its future development please consider donating me (`Giampaolo Rodola' <http://grodola.blogspot.com/p/about.html>`_) some money. + I only ask for a small donation, but of course I appreciate any amount. + + .. image:: http://www.paypal.com/en_US/i/btn/x-click-but04.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 + :alt: Donate via PayPal + + Don't want to donate money? Then maybe you could `write me a recommendation on Linkedin <http://www.linkedin.com/in/grodola>`_. + + ============ + Mailing list + ============ + + http://groups.google.com/group/psutil/ + + ======== + Timeline + ======== + + - 2015-07-15: `psutil-3.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.1.1.tar.gz>`_ + - 2015-07-15: `psutil-3.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.1.0.tar.gz>`_ + - 2015-06-18: `psutil-3.0.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.0.1.tar.gz>`_ + - 2015-06-13: `psutil-3.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.0.0.tar.gz>`_ + - 2015-02-02: `psutil-2.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.2.1.tar.gz>`_ + - 2015-01-06: `psutil-2.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.2.0.tar.gz>`_ + - 2014-09-26: `psutil-2.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.3.tar.gz>`_ + - 2014-09-21: `psutil-2.1.2.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.2.tar.gz>`_ + - 2014-04-30: `psutil-2.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.1.tar.gz>`_ + - 2014-04-08: `psutil-2.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.0.tar.gz>`_ + - 2014-03-10: `psutil-2.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.0.0.tar.gz>`_ + - 2013-11-25: `psutil-1.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.2.1.tar.gz>`_ + - 2013-11-20: `psutil-1.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.2.0.tar.gz>`_ + - 2013-11-07: `psutil-1.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.3.tar.gz>`_ + - 2013-10-22: `psutil-1.1.2.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.2.tar.gz>`_ + - 2013-10-08: `psutil-1.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.1.tar.gz>`_ + - 2013-09-28: `psutil-1.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.0.tar.gz>`_ + - 2013-07-12: `psutil-1.0.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.0.1.tar.gz>`_ + - 2013-07-10: `psutil-1.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.0.0.tar.gz>`_ + - 2013-05-03: `psutil-0.7.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.7.1.tar.gz>`_ + - 2013-04-12: `psutil-0.7.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.7.0.tar.gz>`_ + - 2012-08-16: `psutil-0.6.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.6.1.tar.gz>`_ + - 2012-08-13: `psutil-0.6.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.6.0.tar.gz>`_ + - 2012-06-29: `psutil-0.5.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.5.1.tar.gz>`_ + - 2012-06-27: `psutil-0.5.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.5.0.tar.gz>`_ + - 2011-12-14: `psutil-0.4.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.4.1.tar.gz>`_ + - 2011-10-29: `psutil-0.4.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.4.0.tar.gz>`_ + - 2011-07-08: `psutil-0.3.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.3.0.tar.gz>`_ + - 2011-03-20: `psutil-0.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.2.1.tar.gz>`_ + - 2010-11-13: `psutil-0.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.2.0.tar.gz>`_ + - 2010-03-02: `psutil-0.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.3.tar.gz>`_ + - 2009-05-06: `psutil-0.1.2.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.2.tar.gz>`_ + - 2009-03-06: `psutil-0.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.1.tar.gz>`_ + - 2009-01-27: `psutil-0.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.0.tar.gz>`_ + +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 +Platform: Platform Independent +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Environment :: Win32 (MS Windows) +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 +Classifier: Operating System :: Microsoft +Classifier: Operating System :: OS Independent +Classifier: Operating System :: POSIX :: BSD :: FreeBSD +Classifier: Operating System :: POSIX :: Linux +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 :: 3.0 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: System :: Benchmark +Classifier: Topic :: System :: Hardware +Classifier: Topic :: System :: Monitoring +Classifier: Topic :: System :: Networking :: Monitoring +Classifier: Topic :: System :: Networking +Classifier: Topic :: System :: Systems Administration +Classifier: Topic :: Utilities diff --git a/python/psutil/README.rst b/python/psutil/README.rst new file mode 100644 index 0000000000..564656146c --- /dev/null +++ b/python/psutil/README.rst @@ -0,0 +1,386 @@ +.. image:: https://img.shields.io/pypi/dm/psutil.svg + :target: https://pypi.python.org/pypi/psutil#downloads + :alt: Downloads this month + +.. image:: https://api.travis-ci.org/giampaolo/psutil.png?branch=master + :target: https://travis-ci.org/giampaolo/psutil + :alt: Linux tests (Travis) + +.. image:: https://ci.appveyor.com/api/projects/status/qdwvw7v1t915ywr5/branch/master?svg=true + :target: https://ci.appveyor.com/project/giampaolo/psutil + :alt: Windows tests (Appveyor) + +.. image:: https://coveralls.io/repos/giampaolo/psutil/badge.svg?branch=master&service=github + :target: https://coveralls.io/github/giampaolo/psutil?branch=master + :alt: Test coverage (coverall.io) + +.. image:: https://img.shields.io/pypi/v/psutil.svg + :target: https://pypi.python.org/pypi/psutil/ + :alt: Latest version + +.. image:: https://img.shields.io/github/stars/giampaolo/psutil.svg + :target: https://github.com/giampaolo/psutil/ + :alt: Github stars + +.. image:: https://img.shields.io/scrutinizer/g/giampaolo/psutil.svg + :target: https://scrutinizer-ci.com/g/giampaolo/psutil/ + :alt: Code quality (scrutinizer-ci.com) + +.. image:: https://img.shields.io/pypi/l/psutil.svg + :target: https://pypi.python.org/pypi/psutil/ + :alt: License + +=========== +Quick links +=========== + +- `Home page <https://github.com/giampaolo/psutil>`_ +- `Documentation <http://pythonhosted.org/psutil/>`_ +- `Installation <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_ +- `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`_ +- `Forum <http://groups.google.com/group/psutil/topics>`_ +- `Blog <http://grodola.blogspot.com/search/label/psutil>`_ +- `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_ +- `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`_ + +======= +Summary +======= + +psutil (python system and process utilities) is a cross-platform library for +retrieving information on **running processes** and **system utilization** +(CPU, memory, disks, network) in Python. It is useful mainly for **system +monitoring**, **profiling and limiting process resources** and **management of +running processes**. It implements many functionalities offered by command line +tools such as: ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, +ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap. It currently supports +**Linux, Windows, OSX, FreeBSD** and **Sun Solaris**, both **32-bit** and +**64-bit** architectures, with Python versions from **2.6 to 3.5** (users of +Python 2.4 and 2.5 may use `2.1.3 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__ version). +`PyPy <http://pypy.org/>`__ is also known to work. + +==================== +Example applications +==================== + +.. image:: http://psutil.googlecode.com/svn/wiki/images/top-thumb.png + :target: http://psutil.googlecode.com/svn/wiki/images/top.png + :alt: top + +.. image:: http://psutil.googlecode.com/svn/wiki/images/nettop-thumb.png + :target: http://psutil.googlecode.com/svn/wiki/images/nettop.png + :alt: nettop + +.. image:: http://psutil.googlecode.com/svn/wiki/images/iotop-thumb.png + :target: http://psutil.googlecode.com/svn/wiki/images/iotop.png + :alt: iotop + +See also: + + * https://github.com/nicolargo/glances + * https://github.com/google/grr + * https://github.com/Jahaja/psdash + +============== +Example usages +============== + +CPU +=== + +.. code-block:: python + + >>> 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) + >>> + >>> for x in range(3): + ... psutil.cpu_percent(interval=1) + ... + 4.0 + 5.9 + 3.8 + >>> + >>> for x in range(3): + ... psutil.cpu_percent(interval=1, percpu=True) + ... + [4.0, 6.9, 3.7, 9.2] + [7.0, 8.5, 2.4, 2.1] + [1.2, 9.0, 9.9, 7.2] + >>> + >>> + >>> for x in range(3): + ... psutil.cpu_times_percent(interval=1, percpu=False) + ... + scputimes(user=1.5, nice=0.0, system=0.5, idle=96.5, iowait=1.5, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + scputimes(user=1.0, nice=0.0, system=0.0, idle=99.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + scputimes(user=2.0, nice=0.0, system=0.0, idle=98.0, iowait=0.0, irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + >>> + >>> psutil.cpu_count() + 4 + >>> psutil.cpu_count(logical=False) + 2 + >>> + +Memory +====== + +.. code-block:: python + + >>> psutil.virtual_memory() + svmem(total=8374149120, available=2081050624, percent=75.1, used=8074080256, free=300068864, active=3294920704, inactive=1361616896, buffers=529895424, cached=1251086336) + >>> psutil.swap_memory() + sswap(total=2097147904, used=296128512, free=1801019392, percent=14.1, sin=304193536, sout=677842944) + >>> + +Disks +===== + +.. code-block:: python + + >>> psutil.disk_partitions() + [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid'), + sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw')] + >>> + >>> psutil.disk_usage('/') + sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5) + >>> + >>> psutil.disk_io_counters(perdisk=False) + sdiskio(read_count=719566, write_count=1082197, read_bytes=18626220032, write_bytes=24081764352, read_time=5023392, write_time=63199568) + >>> + +Network +======= + +.. code-block:: python + + >>> psutil.net_io_counters(pernic=True) + {'eth0': netio(bytes_sent=485291293, bytes_recv=6004858642, packets_sent=3251564, packets_recv=4787798, errin=0, errout=0, dropin=0, dropout=0), + 'lo': netio(bytes_sent=2838627, bytes_recv=2838627, packets_sent=30567, packets_recv=30567, errin=0, errout=0, dropin=0, dropout=0)} + >>> + >>> psutil.net_connections() + [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED', pid=1254), + pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING', pid=2987), + pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED', pid=None), + pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT', pid=None) + ...] + >>> + >>> psutil.net_if_addrs() + {'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1'), + snic(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None), + snic(family=<AddressFamily.AF_LINK: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00')], + 'wlan0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255'), + snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None), + snic(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff')]} + >>> + >>> psutil.net_if_stats() + {'eth0': 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)} + +Other system info +================= + +.. code-block:: python + + >>> psutil.users() + [user(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0), + user(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0)] + >>> + >>> psutil.boot_time() + 1365519115.0 + >>> + +Process management +================== + +.. code-block:: python + + >>> import psutil + >>> psutil.pids() + [1, 2, 3, 4, 5, 6, 7, 46, 48, 50, 51, 178, 182, 222, 223, 224, + 268, 1215, 1216, 1220, 1221, 1243, 1244, 1301, 1601, 2237, 2355, + 2637, 2774, 3932, 4176, 4177, 4185, 4187, 4189, 4225, 4243, 4245, + 4263, 4282, 4306, 4311, 4312, 4313, 4314, 4337, 4339, 4357, 4358, + 4363, 4383, 4395, 4408, 4433, 4443, 4445, 4446, 5167, 5234, 5235, + 5252, 5318, 5424, 5644, 6987, 7054, 7055, 7071] + >>> + >>> p = psutil.Process(7055) + >>> p.name() + 'python' + >>> p.exe() + '/usr/bin/python' + >>> p.cwd() + '/home/giampaolo' + >>> p.cmdline() + ['/usr/bin/python', 'main.py'] + >>> + >>> p.status() + 'running' + >>> p.username() + 'giampaolo' + >>> p.create_time() + 1267551141.5019531 + >>> p.terminal() + '/dev/pts/0' + >>> + >>> p.uids() + puids(real=1000, effective=1000, saved=1000) + >>> p.gids() + pgids(real=1000, effective=1000, saved=1000) + >>> + >>> p.cpu_times() + pcputimes(user=1.02, system=0.31) + >>> p.cpu_percent(interval=1.0) + 12.1 + >>> p.cpu_affinity() + [0, 1, 2, 3] + >>> p.cpu_affinity([0]) # set + >>> + >>> p.memory_percent() + 0.63423 + >>> + >>> p.memory_info() + pmem(rss=7471104, vms=68513792) + >>> p.memory_info_ex() + extmem(rss=9662464, vms=49192960, shared=3612672, text=2564096, lib=0, data=5754880, dirty=0) + >>> p.memory_maps() + [pmmap_grouped(path='/lib/x86_64-linux-gnu/libutil-2.15.so', rss=16384, anonymous=8192, swap=0), + pmmap_grouped(path='/lib/x86_64-linux-gnu/libc-2.15.so', rss=6384, anonymous=15, swap=0), + pmmap_grouped(path='/lib/x86_64-linux-gnu/libcrypto.so.1.0.0', rss=34124, anonymous=1245, swap=0), + pmmap_grouped(path='[heap]', rss=54653, anonymous=8192, swap=0), + pmmap_grouped(path='[stack]', rss=1542, anonymous=166, swap=0), + ...] + >>> + >>> p.io_counters() + pio(read_count=478001, write_count=59371, read_bytes=700416, write_bytes=69632) + >>> + >>> p.open_files() + [popenfile(path='/home/giampaolo/svn/psutil/somefile', fd=3)] + >>> + >>> p.connections() + [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED'), + pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING'), + pconn(fd=119, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED'), + pconn(fd=123, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT')] + >>> + >>> p.num_threads() + 4 + >>> p.num_fds() + 8 + >>> p.threads() + [pthread(id=5234, user_time=22.5, system_time=9.2891), + pthread(id=5235, user_time=0.0, system_time=0.0), + pthread(id=5236, user_time=0.0, system_time=0.0), + pthread(id=5237, user_time=0.0707, system_time=1.1)] + >>> + >>> p.num_ctx_switches() + pctxsw(voluntary=78, involuntary=19) + >>> + >>> p.nice() + 0 + >>> p.nice(10) # set + >>> + >>> p.ionice(psutil.IOPRIO_CLASS_IDLE) # IO priority (Win and Linux only) + >>> p.ionice() + pionice(ioclass=<IOPriority.IOPRIO_CLASS_IDLE: 3>, value=0) + >>> + >>> p.rlimit(psutil.RLIMIT_NOFILE, (5, 5)) # set resource limits (Linux only) + >>> p.rlimit(psutil.RLIMIT_NOFILE) + (5, 5) + >>> + >>> p.suspend() + >>> p.resume() + >>> + >>> p.terminate() + >>> p.wait(timeout=3) + 0 + >>> + >>> psutil.test() + USER PID %CPU %MEM VSZ RSS TTY START TIME COMMAND + root 1 0.0 0.0 24584 2240 Jun17 00:00 init + root 2 0.0 0.0 0 0 Jun17 00:00 kthreadd + root 3 0.0 0.0 0 0 Jun17 00:05 ksoftirqd/0 + ... + giampaolo 31475 0.0 0.0 20760 3024 /dev/pts/0 Jun19 00:00 python2.4 + giampaolo 31721 0.0 2.2 773060 181896 00:04 10:30 chrome + root 31763 0.0 0.0 0 0 00:05 00:00 kworker/0:1 + >>> + +Further process APIs +==================== + +.. code-block:: python + + >>> for p in psutil.process_iter(): + ... print(p) + ... + psutil.Process(pid=1, name='init') + psutil.Process(pid=2, name='kthreadd') + psutil.Process(pid=3, name='ksoftirqd/0') + ... + >>> + >>> def on_terminate(proc): + ... print("process {} terminated".format(proc)) + ... + >>> # waits for multiple processes to terminate + >>> gone, alive = psutil.wait_procs(procs_list, 3, callback=on_terminate) + >>> + +====== +Donate +====== + +A lot of time and effort went into making psutil as it is right now. +If you feel psutil is useful to you or your business and want to support its future development please consider donating me (`Giampaolo Rodola' <http://grodola.blogspot.com/p/about.html>`_) some money. +I only ask for a small donation, but of course I appreciate any amount. + +.. image:: http://www.paypal.com/en_US/i/btn/x-click-but04.gif + :target: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 + :alt: Donate via PayPal + +Don't want to donate money? Then maybe you could `write me a recommendation on Linkedin <http://www.linkedin.com/in/grodola>`_. + +============ +Mailing list +============ + +http://groups.google.com/group/psutil/ + +======== +Timeline +======== + +- 2015-07-15: `psutil-3.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.1.1.tar.gz>`_ +- 2015-07-15: `psutil-3.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.1.0.tar.gz>`_ +- 2015-06-18: `psutil-3.0.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.0.1.tar.gz>`_ +- 2015-06-13: `psutil-3.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-3.0.0.tar.gz>`_ +- 2015-02-02: `psutil-2.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.2.1.tar.gz>`_ +- 2015-01-06: `psutil-2.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.2.0.tar.gz>`_ +- 2014-09-26: `psutil-2.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.3.tar.gz>`_ +- 2014-09-21: `psutil-2.1.2.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.2.tar.gz>`_ +- 2014-04-30: `psutil-2.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.1.tar.gz>`_ +- 2014-04-08: `psutil-2.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.1.0.tar.gz>`_ +- 2014-03-10: `psutil-2.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-2.0.0.tar.gz>`_ +- 2013-11-25: `psutil-1.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.2.1.tar.gz>`_ +- 2013-11-20: `psutil-1.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.2.0.tar.gz>`_ +- 2013-11-07: `psutil-1.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.3.tar.gz>`_ +- 2013-10-22: `psutil-1.1.2.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.2.tar.gz>`_ +- 2013-10-08: `psutil-1.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.1.tar.gz>`_ +- 2013-09-28: `psutil-1.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.1.0.tar.gz>`_ +- 2013-07-12: `psutil-1.0.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.0.1.tar.gz>`_ +- 2013-07-10: `psutil-1.0.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-1.0.0.tar.gz>`_ +- 2013-05-03: `psutil-0.7.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.7.1.tar.gz>`_ +- 2013-04-12: `psutil-0.7.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.7.0.tar.gz>`_ +- 2012-08-16: `psutil-0.6.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.6.1.tar.gz>`_ +- 2012-08-13: `psutil-0.6.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.6.0.tar.gz>`_ +- 2012-06-29: `psutil-0.5.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.5.1.tar.gz>`_ +- 2012-06-27: `psutil-0.5.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.5.0.tar.gz>`_ +- 2011-12-14: `psutil-0.4.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.4.1.tar.gz>`_ +- 2011-10-29: `psutil-0.4.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.4.0.tar.gz>`_ +- 2011-07-08: `psutil-0.3.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.3.0.tar.gz>`_ +- 2011-03-20: `psutil-0.2.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.2.1.tar.gz>`_ +- 2010-11-13: `psutil-0.2.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.2.0.tar.gz>`_ +- 2010-03-02: `psutil-0.1.3.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.3.tar.gz>`_ +- 2009-05-06: `psutil-0.1.2.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.2.tar.gz>`_ +- 2009-03-06: `psutil-0.1.1.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.1.tar.gz>`_ +- 2009-01-27: `psutil-0.1.0.tar.gz <https://pypi.python.org/packages/source/p/psutil/psutil-0.1.0.tar.gz>`_ diff --git a/python/psutil/TODO b/python/psutil/TODO new file mode 100644 index 0000000000..a5df809d02 --- /dev/null +++ b/python/psutil/TODO @@ -0,0 +1,167 @@ +TODO +==== + +A collection of ideas and notes about stuff to implement in future versions. +"#NNN" occurrences refer to bug tracker issues at: +https://github.com/giampaolo/psutil/issues + + +HIGHER PRIORITY +=============== + + * OpenBSD support. + + * #371: CPU temperature (apparently OSX and Linux only; on Linux it requires + lm-sensors lib). + + * #269: expose network ifaces RX/TW queues. This should probably go into + net_if_stats(). Figure out on what platforms this is supported: + Linux: yes + Others: ? + + * Process.threads(): thread names; patch for OSX available at: + https://code.google.com/p/plcrashreporter/issues/detail?id=65 + + * Asynchronous psutil.Popen (see http://bugs.python.org/issue1191964) + + * (Windows) fall back on using WMIC for Process methods returning AccessDenied + + * #613: thread names. + + * #604: emulate os.getloadavg() on Windows + + * #269: NIC rx/tx queue. + + +LOWER PRIORITY +============== + + * #355: Android support. + + * #276: GNU/Hurd support. + + * #429: NetBSD support. + + * DragonFlyBSD support? + + * AIX support? + + * examples/taskmgr-gui.py (using tk). + + * system-wide number of open file descriptors: + * https://jira.hyperic.com/browse/SIGAR-30 + * http://www.netadmintools.com/part295.html + + * Number of system threads. + * Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684824(v=vs.85).aspx + + * #357: what CPU a process is on. + + * Doc / wiki which compares similarities between UNIX cli tools and psutil. + Example: + df -a -> psutil.disk_partitions + lsof -> psutil.Process.open_files() and psutil.Process.open_connections() + killall-> (actual script) + tty -> psutil.Process.terminal() + who -> psutil.users() + + +DEBATABLE +========= + + * psutil.proc_tree() something which obtains a {pid:ppid, ...} dict for + all running processes in one shot. This can be factored out from + Process.children() and exposed as a first class function. + PROS: on Windows we can take advantage of _psutil_windows.ppid_map() + which is faster than iterating over all pids and calling ppid(). + CONS: examples/pstree.py shows this can be easily done in the user code + so maybe it's not worth the addition. + + * advanced cmdline interface exposing the whole API and providing different + kind of outputs (e.g. pprinted, colorized, json). + + * [Linux]: process cgroups (http://en.wikipedia.org/wiki/Cgroups). They look + similar to prlimit() in terms of functionality but uglier (they should allow + limiting per-process network IO resources though, which is great). Needs + further reading. + + * Should we expose OS constants (psutil.WINDOWS, psutil.OSX etc.)? + + * Python 3.3. exposed different sched.h functions: + http://docs.python.org/dev/whatsnew/3.3.html#os + http://bugs.python.org/issue12655 + http://docs.python.org/dev/library/os.html#interface-to-the-scheduler + It might be worth to take a look and figure out whether we can include some + of those in psutil. + Also, we can probably reimplement wait_pid() on POSIX which is currently + implemented as a busy-loop. + + * Certain systems provide CPU times about process children. On those systems + Process.cpu_times() might return a (user, system, user_children, + system_children) ntuple. + * Linux: /proc/{PID}/stat + * Solaris: pr_cutime and pr_cstime + * FreeBSD: none + * OSX: none + * Windows: none + + * ...also, os.times() provides 'elapsed' times as well. + + * ...also Linux provides guest_time and cguest_time. + + * Enrich exception classes hierarchy on Python >= 3.3 / post PEP-3151 so that: + - NoSuchProcess inherits from ProcessLookupError + - AccessDenied inherits from PermissionError + - TimeoutExpired inherits from TimeoutError (debatable) + See: http://docs.python.org/3/library/exceptions.html#os-exceptions + + * Process.threads() might grow an extra "id" parameter so that it can be + used as such: + + >>> p = psutil.Process(os.getpid()) + >>> p.threads(id=psutil.current_thread_id()) + thread(id=2539, user_time=0.03, system_time=0.02) + >>> + + Note: this leads to questions such as "should we have a custom NoSuchThread + exception? Also see issue #418. + + Note #2: this would work with os.getpid() only. + psutil.current_thread_id() might be desirable as per issue #418 though. + + * should psutil.TimeoutExpired exception have a 'msg' kwarg similar to + NoSuchProcess and AccessDenied? Not that we need it, but currently we + cannot raise a TimeoutExpired exception with a specific error string. + + * process_iter() might grow an "attrs" parameter similar to Process.as_dict() + invoke the necessary methods and include the results into a "cache" + attribute attached to the returned Process instances so that one can avoid + catching NSP and AccessDenied: + for p in process_iter(attrs=['cpu_percent']): + print(p.cache['cpu_percent']) + This also leads questions as whether we should introduce a sorting order. + + * round Process.memory_percent() result? + + * #550: number of threads per core. + + * Have psutil.Process().cpu_affinity([]) be an alias for "all CPUs"? + + +COMPATIBILITY BREAKAGE +====================== + +Removals (will likely happen in 2.2): + + * (S) psutil.Process.nice (deprecated in 0.5.0) + * (S) get_process_list (deprecated in 0.5.0) + * (S) psutil.*mem* functions (deprecated in 0.3.0 and 0.6.0) + * (M) psutil.network_io_counters (deprecated in 1.0.0) + * (M) local_address and remote_address Process.connection() namedtuple fields + (deprecated in 1.0.0) + + +REJECTED IDEAS +============== + +STUB diff --git a/python/psutil/docs/Makefile b/python/psutil/docs/Makefile new file mode 100644 index 0000000000..b23ab4ba8a --- /dev/null +++ b/python/psutil/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make <target>' where <target> is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR) + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/psutil.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/psutil.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/psutil" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/psutil" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/python/psutil/docs/README b/python/psutil/docs/README new file mode 100644 index 0000000000..3aaea8a5ba --- /dev/null +++ b/python/psutil/docs/README @@ -0,0 +1,15 @@ +About +===== + +This directory contains the reStructuredText (reST) sources to the psutil +documentation. You don't need to build them yourself, prebuilt versions are +available at https://pythonhosted.org/psutil/. +In case you want, you need to install sphinx first: + + $ pip install sphinx + +Then run: + + $ make html + +You'll then have an HTML version of the doc at _build/html/index.html.
\ No newline at end of file diff --git a/python/psutil/docs/_static/copybutton.js b/python/psutil/docs/_static/copybutton.js new file mode 100644 index 0000000000..5d82c672be --- /dev/null +++ b/python/psutil/docs/_static/copybutton.js @@ -0,0 +1,57 @@ +$(document).ready(function() { + /* Add a [>>>] button on the top-right corner of code samples to hide + * the >>> and ... prompts and the output and thus make the code + * copyable. */ + var div = $('.highlight-python .highlight,' + + '.highlight-python3 .highlight') + var pre = div.find('pre'); + + // get the styles from the current theme + pre.parent().parent().css('position', 'relative'); + var hide_text = 'Hide the prompts and output'; + var show_text = 'Show the prompts and output'; + var border_width = pre.css('border-top-width'); + var border_style = pre.css('border-top-style'); + var border_color = pre.css('border-top-color'); + var button_styles = { + 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', + 'border-color': border_color, 'border-style': border_style, + 'border-width': border_width, 'color': border_color, 'text-size': '75%', + 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em', + 'border-radius': '0 3px 0 0' + } + + // create and add the button to all the code blocks that contain >>> + div.each(function(index) { + var jthis = $(this); + if (jthis.find('.gp').length > 0) { + var button = $('<span class="copybutton">>>></span>'); + button.css(button_styles) + button.attr('title', hide_text); + jthis.prepend(button); + } + // tracebacks (.gt) contain bare text elements that need to be + // wrapped in a span to work with .nextUntil() (see later) + jthis.find('pre:has(.gt)').contents().filter(function() { + return ((this.nodeType == 3) && (this.data.trim().length > 0)); + }).wrap('<span>'); + }); + + // define the behavior of the button when it's clicked + $('.copybutton').toggle( + function() { + var button = $(this); + button.parent().find('.go, .gp, .gt').hide(); + button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); + button.css('text-decoration', 'line-through'); + button.attr('title', show_text); + }, + function() { + var button = $(this); + button.parent().find('.go, .gp, .gt').show(); + button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); + button.css('text-decoration', 'none'); + button.attr('title', hide_text); + }); +}); + diff --git a/python/psutil/docs/_static/favicon.ico b/python/psutil/docs/_static/favicon.ico Binary files differnew file mode 100644 index 0000000000..c9efc5844a --- /dev/null +++ b/python/psutil/docs/_static/favicon.ico diff --git a/python/psutil/docs/_static/logo.png b/python/psutil/docs/_static/logo.png Binary files differnew file mode 100644 index 0000000000..7d975ec9d2 --- /dev/null +++ b/python/psutil/docs/_static/logo.png diff --git a/python/psutil/docs/_static/sidebar.js b/python/psutil/docs/_static/sidebar.js new file mode 100644 index 0000000000..3376963911 --- /dev/null +++ b/python/psutil/docs/_static/sidebar.js @@ -0,0 +1,161 @@ +/* + * sidebar.js + * ~~~~~~~~~~ + * + * This script makes the Sphinx sidebar collapsible. + * + * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds in + * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to + * collapse and expand the sidebar. + * + * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the + * width of the sidebar and the margin-left of the document are decreased. + * When the sidebar is expanded the opposite happens. This script saves a + * per-browser/per-session cookie used to remember the position of the sidebar + * among the pages. Once the browser is closed the cookie is deleted and the + * position reset to the default (expanded). + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +$(function() { + // global elements used by the functions. + // the 'sidebarbutton' element is defined as global after its + // creation, in the add_sidebar_button function + var bodywrapper = $('.bodywrapper'); + var sidebar = $('.sphinxsidebar'); + var sidebarwrapper = $('.sphinxsidebarwrapper'); + + // original margin-left of the bodywrapper and width of the sidebar + // with the sidebar expanded + var bw_margin_expanded = bodywrapper.css('margin-left'); + var ssb_width_expanded = sidebar.width(); + + // margin-left of the bodywrapper and width of the sidebar + // with the sidebar collapsed + var bw_margin_collapsed = '.8em'; + var ssb_width_collapsed = '.8em'; + + // colors used by the current theme + var dark_color = '#AAAAAA'; + var light_color = '#CCCCCC'; + + function sidebar_is_collapsed() { + return sidebarwrapper.is(':not(:visible)'); + } + + function toggle_sidebar() { + if (sidebar_is_collapsed()) + expand_sidebar(); + else + collapse_sidebar(); + } + + function collapse_sidebar() { + sidebarwrapper.hide(); + sidebar.css('width', ssb_width_collapsed); + bodywrapper.css('margin-left', bw_margin_collapsed); + sidebarbutton.css({ + 'margin-left': '0', + //'height': bodywrapper.height(), + 'height': sidebar.height(), + 'border-radius': '5px' + }); + sidebarbutton.find('span').text('»'); + sidebarbutton.attr('title', _('Expand sidebar')); + document.cookie = 'sidebar=collapsed'; + } + + function expand_sidebar() { + bodywrapper.css('margin-left', bw_margin_expanded); + sidebar.css('width', ssb_width_expanded); + sidebarwrapper.show(); + sidebarbutton.css({ + 'margin-left': ssb_width_expanded-12, + //'height': bodywrapper.height(), + 'height': sidebar.height(), + 'border-radius': '0 5px 5px 0' + }); + sidebarbutton.find('span').text('«'); + sidebarbutton.attr('title', _('Collapse sidebar')); + //sidebarwrapper.css({'padding-top': + // Math.max(window.pageYOffset - sidebarwrapper.offset().top, 10)}); + document.cookie = 'sidebar=expanded'; + } + + function add_sidebar_button() { + sidebarwrapper.css({ + 'float': 'left', + 'margin-right': '0', + 'width': ssb_width_expanded - 28 + }); + // create the button + sidebar.append( + '<div id="sidebarbutton"><span>«</span></div>' + ); + var sidebarbutton = $('#sidebarbutton'); + // find the height of the viewport to center the '<<' in the page + var viewport_height; + if (window.innerHeight) + viewport_height = window.innerHeight; + else + viewport_height = $(window).height(); + var sidebar_offset = sidebar.offset().top; + + var sidebar_height = sidebar.height(); + //var sidebar_height = Math.max(bodywrapper.height(), sidebar.height()); + sidebarbutton.find('span').css({ + 'display': 'block', + 'margin-top': sidebar_height/2 - 10 + //'margin-top': (viewport_height - sidebar.position().top - 20) / 2 + //'position': 'fixed', + //'top': Math.min(viewport_height/2, sidebar_height/2 + sidebar_offset) - 10 + }); + + sidebarbutton.click(toggle_sidebar); + sidebarbutton.attr('title', _('Collapse sidebar')); + sidebarbutton.css({ + 'border-radius': '0 5px 5px 0', + 'color': '#444444', + 'background-color': '#CCCCCC', + 'font-size': '1.2em', + 'cursor': 'pointer', + 'height': sidebar_height, + 'padding-top': '1px', + 'padding-left': '1px', + 'margin-left': ssb_width_expanded - 12 + }); + + sidebarbutton.hover( + function () { + $(this).css('background-color', dark_color); + }, + function () { + $(this).css('background-color', light_color); + } + ); + } + + function set_position_from_cookie() { + if (!document.cookie) + return; + var items = document.cookie.split(';'); + for(var k=0; k<items.length; k++) { + var key_val = items[k].split('='); + var key = key_val[0]; + if (key == 'sidebar') { + var value = key_val[1]; + if ((value == 'collapsed') && (!sidebar_is_collapsed())) + collapse_sidebar(); + else if ((value == 'expanded') && (sidebar_is_collapsed())) + expand_sidebar(); + } + } + } + + add_sidebar_button(); + var sidebarbutton = $('#sidebarbutton'); + set_position_from_cookie(); +}); diff --git a/python/psutil/docs/_template/globaltoc.html b/python/psutil/docs/_template/globaltoc.html new file mode 100644 index 0000000000..f5fbb406cc --- /dev/null +++ b/python/psutil/docs/_template/globaltoc.html @@ -0,0 +1,12 @@ +{# + basic/globaltoc.html + ~~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: global table of contents. + + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +<h3>{{ _('Manual') }}</h3> +{{ toctree() }} +<a href="{{ pathto(master_doc) }}">Back to Welcome</a> diff --git a/python/psutil/docs/_template/indexcontent.html b/python/psutil/docs/_template/indexcontent.html new file mode 100644 index 0000000000..dd5e7249a0 --- /dev/null +++ b/python/psutil/docs/_template/indexcontent.html @@ -0,0 +1,4 @@ +{% extends "defindex.html" %} +{% block tables %} + +{% endblock %} diff --git a/python/psutil/docs/_template/indexsidebar.html b/python/psutil/docs/_template/indexsidebar.html new file mode 100644 index 0000000000..903675d100 --- /dev/null +++ b/python/psutil/docs/_template/indexsidebar.html @@ -0,0 +1,8 @@ +<h3>Useful links</h3> +<ul> + <li><a href="https://github.com/giampaolo/psutil">Github project</a></li> + <li><a href="http://grodola.blogspot.com/search/label/psutil">Blog</a></li> + <li><a href="https://pypi.python.org/pypi?:action=display&name=psutil#downloads">Download</a></li> + <li><a href="https://github.com/giampaolo/psutil/issues">Issues</a></li> + <li><a href="http://groups.google.com/group/psutil/topics">Forum</a></li> +</ul> diff --git a/python/psutil/docs/_template/page.html b/python/psutil/docs/_template/page.html new file mode 100644 index 0000000000..04b47b4153 --- /dev/null +++ b/python/psutil/docs/_template/page.html @@ -0,0 +1,66 @@ +{% extends "!page.html" %} +{% block extrahead %} +{{ super() }} +{% if not embedded %}<script type="text/javascript" src="{{ pathto('_static/copybutton.js', 1) }}"></script>{% endif %} +<script type="text/javascript"> + + // Store editor pop-up help state in localStorage + // so it does not re-pop-up itself between page loads. + // Do not even to pretend to support IE gracefully. + (function($) { + + $(document).ready(function() { + var box = $("#editor-trap"); + var klass = "toggled"; + var storageKey = "toggled"; + + function toggle() { + box.toggleClass(klass); + // Store the toggle status in local storage as "has value string" or null + window.localStorage.setItem(storageKey, box.hasClass(klass) ? "toggled" : "not-toggled"); + } + + box.click(toggle); + + // Check the persistent state of the editor pop-up + // Note that localStorage does not necessarily support boolean values (ugh!) + // http://stackoverflow.com/questions/3263161/cannot-set-boolean-values-in-localstorage + var v = window.localStorage.getItem(storageKey); + if(v == "toggled" || !v) { + box.addClass(klass); + } + + }); + + })(jQuery); +</script> +<script type="text/javascript"> + + var _gaq = _gaq || []; + _gaq.push(['_setAccount', 'UA-2097050-4']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); + +</script> +{% endblock %} + +{% block rootrellink %} + <li><a href="https://github.com/giampaolo/psutil/"><img src="{{ pathto('_static/logo.png', 1) }}" style="height: 30px; vertical-align: middle; padding-right: 1em;" /> Project Homepage</a>{{ reldelim1 }}</li> + <li><a href="{{ pathto('index') }}">{{ shorttitle }}</a>{{ reldelim1 }}</li> +{% endblock %} + + +{% block footer %} +<div class="footer"> + © Copyright {{ copyright|e }}. + <br /> + Last updated on {{ last_updated|e }}. + <br /> + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> {{ sphinx_version|e }}. +</div> +{% endblock %}
\ No newline at end of file diff --git a/python/psutil/docs/_themes/pydoctheme/static/pydoctheme.css b/python/psutil/docs/_themes/pydoctheme/static/pydoctheme.css new file mode 100644 index 0000000000..4196e5582c --- /dev/null +++ b/python/psutil/docs/_themes/pydoctheme/static/pydoctheme.css @@ -0,0 +1,187 @@ +@import url("default.css"); + +body { + background-color: white; + margin-left: 1em; + margin-right: 1em; +} + +div.related { + margin-bottom: 1.2em; + padding: 0.5em 0; + border-top: 1px solid #ccc; + margin-top: 0.5em; +} + +div.related a:hover { + color: #0095C4; +} + +div.related:first-child { + border-top: 0; + padding-top: 0; + border-bottom: 1px solid #ccc; +} + +div.sphinxsidebar { + background-color: #eeeeee; + border-radius: 5px; + line-height: 130%; + font-size: smaller; +} + +div.sphinxsidebar h3, div.sphinxsidebar h4 { + margin-top: 1.5em; +} + +div.sphinxsidebarwrapper > h3:first-child { + margin-top: 0.2em; +} + +div.sphinxsidebarwrapper > ul > li > ul > li { + margin-bottom: 0.4em; +} + +div.sphinxsidebar a:hover { + color: #0095C4; +} + +div.sphinxsidebar input { + font-family: 'Lucida Grande','Lucida Sans','DejaVu Sans',Arial,sans-serif; + border: 1px solid #999999; + font-size: smaller; + border-radius: 3px; +} + +div.sphinxsidebar input[type=text] { + max-width: 150px; +} + +div.body { + padding: 0 0 0 1.2em; +} + +div.body p { + line-height: 140%; +} + +div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { + margin: 0; + border: 0; + padding: 0.3em 0; +} + +div.body hr { + border: 0; + background-color: #ccc; + height: 1px; +} + +div.body pre { + border-radius: 3px; + border: 1px solid #ac9; +} + +div.body div.admonition, div.body div.impl-detail { + border-radius: 3px; +} + +div.body div.impl-detail > p { + margin: 0; +} + +div.body div.seealso { + border: 1px solid #dddd66; +} + +div.body a { + color: #00608f; +} + +div.body a:visited { + color: #30306f; +} + +div.body a:hover { + color: #00B0E4; +} + +tt, pre { + font-family: monospace, sans-serif; + font-size: 96.5%; +} + +div.body tt { + border-radius: 3px; +} + +div.body tt.descname { + font-size: 120%; +} + +div.body tt.xref, div.body a tt { + font-weight: normal; +} + +p.deprecated { + border-radius: 3px; +} + +table.docutils { + border: 1px solid #ddd; + min-width: 20%; + border-radius: 3px; + margin-top: 10px; + margin-bottom: 10px; +} + +table.docutils td, table.docutils th { + border: 1px solid #ddd !important; + border-radius: 3px; +} + +table p, table li { + text-align: left !important; +} + +table.docutils th { + background-color: #eee; + padding: 0.3em 0.5em; +} + +table.docutils td { + background-color: white; + padding: 0.3em 0.5em; +} + +table.footnote, table.footnote td { + border: 0 !important; +} + +div.footer { + line-height: 150%; + margin-top: -2em; + text-align: right; + width: auto; + margin-right: 10px; +} + +div.footer a:hover { + color: #0095C4; +} + +div.body h1, +div.body h2, +div.body h3 { + background-color: #EAEAEA; + border-bottom: 1px solid #CCC; + padding-top: 2px; + padding-bottom: 2px; + padding-left: 5px; + margin-top: 5px; + margin-bottom: 5px; +} + +div.body h2 { + padding-left:10px; +} diff --git a/python/psutil/docs/_themes/pydoctheme/theme.conf b/python/psutil/docs/_themes/pydoctheme/theme.conf new file mode 100644 index 0000000000..95b97e5369 --- /dev/null +++ b/python/psutil/docs/_themes/pydoctheme/theme.conf @@ -0,0 +1,23 @@ +[theme] +inherit = default +stylesheet = pydoctheme.css +pygments_style = sphinx + +[options] +bodyfont = 'Lucida Grande', 'Lucida Sans', 'DejaVu Sans', Arial, sans-serif +headfont = 'Lucida Grande', 'Lucida Sans', 'DejaVu Sans', Arial, sans-serif +footerbgcolor = white +footertextcolor = #555555 +relbarbgcolor = white +relbartextcolor = #666666 +relbarlinkcolor = #444444 +sidebarbgcolor = white +sidebartextcolor = #444444 +sidebarlinkcolor = #444444 +bgcolor = white +textcolor = #222222 +linkcolor = #0090c0 +visitedlinkcolor = #00608f +headtextcolor = #1a1a1a +headbgcolor = white +headlinkcolor = #aaaaaa diff --git a/python/psutil/docs/conf.py b/python/psutil/docs/conf.py new file mode 100644 index 0000000000..9fa163b65e --- /dev/null +++ b/python/psutil/docs/conf.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# +# psutil documentation build configuration file, created by +# sphinx-quickstart. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import datetime +import os + + +PROJECT_NAME = "psutil" +AUTHOR = "Giampaolo Rodola'" +THIS_YEAR = str(datetime.datetime.now().year) +HERE = os.path.abspath(os.path.dirname(__file__)) + + +def get_version(): + INIT = os.path.abspath(os.path.join(HERE, '../psutil/__init__.py')) + with open(INIT, 'r') as f: + for line in f: + if line.startswith('__version__'): + ret = eval(line.strip().split(' = ')[1]) + assert ret.count('.') == 2, ret + for num in ret.split('.'): + assert num.isdigit(), ret + return ret + else: + raise ValueError("couldn't find version string") + +VERSION = get_version() + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.pngmath', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_template'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = PROJECT_NAME +copyright = '2009-%s, %s' % (THIS_YEAR, AUTHOR) + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = VERSION + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +autodoc_docstring_signature = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme = 'pydoctheme' +html_theme_options = {'collapsiblesidebar': True} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ["_themes"] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +html_title = "{project} {version} documentation".format(**locals()) + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = 'logo.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = '_static/favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + 'index': 'indexsidebar.html', + '**': ['globaltoc.html', + 'relations.html', + 'sourcelink.html', + 'searchbox.html'] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = { +# 'index': 'indexcontent.html', +# } + +# If false, no module index is generated. +html_domain_indices = False + +# If false, no index is generated. +html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = '%s-doc' % PROJECT_NAME + +# -- Options for LaTeX output ------------------------------------------------ + +# The paper size ('letter' or 'a4'). +# latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +# latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', '%s.tex' % PROJECT_NAME, + '%s documentation' % PROJECT_NAME, AUTHOR), +] + +# The name of an image file (relative to this directory) to place at +# the top of the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +# latex_preamble = '' + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', PROJECT_NAME, '%s documentation' % PROJECT_NAME, [AUTHOR], 1) +] + +# If true, show URL addresses after external links. +# man_show_urls = False diff --git a/python/psutil/docs/index.rst b/python/psutil/docs/index.rst new file mode 100644 index 0000000000..4430192267 --- /dev/null +++ b/python/psutil/docs/index.rst @@ -0,0 +1,1400 @@ +.. module:: psutil + :synopsis: psutil module +.. moduleauthor:: Giampaolo Rodola' <grodola@gmail.com> + +.. warning:: + + This documentation refers to new 2.X version of psutil. + Instructions on how to port existing 1.2.1 code are + `here <http://grodola.blogspot.com/2014/01/psutil-20-porting.html>`__. + Old 1.2.1 documentation is still available + `here <https://code.google.com/p/psutil/wiki/Documentation>`__. + +psutil documentation +==================== + +Quick links +----------- + +* `Home page <https://github.com/giampaolo/psutil>`__ +* `Blog <http://grodola.blogspot.com/search/label/psutil>`__ +* `Forum <http://groups.google.com/group/psutil/topics>`__ +* `Download <https://pypi.python.org/pypi?:action=display&name=psutil#downloads>`__ +* `Installation <https://github.com/giampaolo/psutil/blob/master/INSTALL.rst>`_ +* `Development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_ +* `What's new <https://github.com/giampaolo/psutil/blob/master/HISTORY.rst>`__ + +About +----- + +From project's home page: + + psutil (python system and process utilities) is a cross-platform library for + retrieving information on running + **processes** and **system utilization** (CPU, memory, disks, network) in + **Python**. + It is useful mainly for **system monitoring**, **profiling** and **limiting + process resources** and **management of running processes**. + It implements many functionalities offered by command line tools + such as: *ps, top, lsof, netstat, ifconfig, who, df, kill, free, nice, + ionice, iostat, iotop, uptime, pidof, tty, taskset, pmap*. + It currently supports **Linux, Windows, OSX, FreeBSD** and **Sun Solaris**, + both **32-bit** and **64-bit** architectures, with Python versions from + **2.6 to 3.4** (users of Python 2.4 and 2.5 may use `2.1.3 <https://pypi.python.org/pypi?name=psutil&version=2.1.3&:action=files>`__ version). + `PyPy <http://pypy.org/>`__ is also known to work. + +The psutil documentation you're reading is distributed as a single HTML page. + +System related functions +======================== + +CPU +--- + +.. function:: cpu_times(percpu=False) + + Return system CPU times as a namedtuple. + Every attribute represents the seconds the CPU has spent in the given mode. + The attributes availability varies depending on the platform: + + - **user** + - **system** + - **idle** + - **nice** *(UNIX)* + - **iowait** *(Linux)* + - **irq** *(Linux, FreeBSD)* + - **softirq** *(Linux)* + - **steal** *(Linux 2.6.11+)* + - **guest** *(Linux 2.6.24+)* + - **guest_nice** *(Linux 3.2.0+)* + + When *percpu* is ``True`` return a list of namedtuples for each logical CPU + on the system. + First element of the list refers to first CPU, second element to second CPU + and so on. + The order of the list is consistent across calls. + Example output on Linux: + + >>> import psutil + >>> psutil.cpu_times() + scputimes(user=17411.7, nice=77.99, system=3797.02, idle=51266.57, iowait=732.58, irq=0.01, softirq=142.43, steal=0.0, guest=0.0, guest_nice=0.0) + +.. function:: cpu_percent(interval=None, percpu=False) + + Return a float representing the current system-wide CPU utilization as a + percentage. When *interval* is > ``0.0`` compares system CPU times elapsed + before and after the interval (blocking). + When *interval* is ``0.0`` or ``None`` compares system CPU times elapsed + since last call or module import, returning immediately. + That means the first time this is called it will return a meaningless ``0.0`` + value which you are supposed to ignore. + In this case is recommended for accuracy that this function be called with at + least ``0.1`` seconds between calls. + When *percpu* is ``True`` returns a list of floats representing the + utilization as a percentage for each CPU. + First element of the list refers to first CPU, second element to second CPU + and so on. The order of the list is consistent across calls. + + >>> import psutil + >>> # blocking + >>> psutil.cpu_percent(interval=1) + 2.0 + >>> # non-blocking (percentage since last call) + >>> psutil.cpu_percent(interval=None) + 2.9 + >>> # blocking, per-cpu + >>> psutil.cpu_percent(interval=1, percpu=True) + [2.0, 1.0] + >>> + + .. warning:: + + the first time this function is called with *interval* = ``0.0`` or ``None`` + it will return a meaningless ``0.0`` value which you are supposed to + ignore. + +.. function:: cpu_times_percent(interval=None, percpu=False) + + Same as :func:`cpu_percent()` but provides utilization percentages for each + specific CPU time as is returned by + :func:`psutil.cpu_times(percpu=True)<cpu_times()>`. + *interval* and + *percpu* arguments have the same meaning as in :func:`cpu_percent()`. + + .. warning:: + + the first time this function is called with *interval* = ``0.0`` or + ``None`` it will return a meaningless ``0.0`` value which you are supposed + to ignore. + +.. function:: cpu_count(logical=True) + + Return the number of logical CPUs in the system (same as + `os.cpu_count() <http://docs.python.org/3/library/os.html#os.cpu_count>`__ + in Python 3.4). + If *logical* is ``False`` return the number of physical cores only (hyper + thread CPUs are excluded). Return ``None`` if undetermined. + + >>> import psutil + >>> psutil.cpu_count() + 4 + >>> psutil.cpu_count(logical=False) + 2 + >>> + +Memory +------ + +.. function:: virtual_memory() + + Return statistics about system memory usage as a namedtuple including the + following fields, expressed in bytes: + + - **total**: total physical memory available. + - **available**: the actual amount of available memory that can be given + instantly to processes that request more memory in bytes; this is + calculated by summing different memory values depending on the platform + (e.g. free + buffers + cached on Linux) and it is supposed to be used to + monitor actual memory usage in a cross platform fashion. + - **percent**: the percentage usage calculated as + ``(total - available) / total * 100``. + - **used**: memory used, calculated differently depending on the platform and + designed for informational purposes only. + - **free**: memory not being used at all (zeroed) that is readily available; + note that this doesn't reflect the actual memory available (use 'available' + instead). + + Platform-specific fields: + + - **active**: (UNIX): memory currently in use or very recently used, and so + it is in RAM. + - **inactive**: (UNIX): memory that is marked as not used. + - **buffers**: (Linux, BSD): cache for things like file system metadata. + - **cached**: (Linux, BSD): cache for various things. + - **wired**: (BSD, OSX): memory that is marked to always stay in RAM. It is + never moved to disk. + - **shared**: (BSD): memory that may be simultaneously accessed by multiple + processes. + + The sum of **used** and **available** does not necessarily equal **total**. + On Windows **available** and **free** are the same. + See `examples/meminfo.py <https://github.com/giampaolo/psutil/blob/master/examples/meminfo.py>`__ + script providing an example on how to convert bytes in a human readable form. + + >>> import psutil + >>> mem = psutil.virtual_memory() + >>> mem + svmem(total=8374149120L, available=1247768576L, percent=85.1, used=8246628352L, free=127520768L, active=3208777728, inactive=1133408256, buffers=342413312L, cached=777834496) + >>> + >>> THRESHOLD = 100 * 1024 * 1024 # 100MB + >>> if mem.available <= THRESHOLD: + ... print("warning") + ... + >>> + + +.. function:: swap_memory() + + Return system swap memory statistics as a namedtuple including the following + fields: + + * **total**: total swap memory in bytes + * **used**: used swap memory in bytes + * **free**: free swap memory in bytes + * **percent**: the percentage usage calculated as ``(total - available) / total * 100`` + * **sin**: the number of bytes the system has swapped in from disk + (cumulative) + * **sout**: the number of bytes the system has swapped out from disk + (cumulative) + + **sin** and **sout** on Windows are meaningless and are always set to ``0``. + See `examples/meminfo.py <https://github.com/giampaolo/psutil/blob/master/examples/meminfo.py>`__ + script providing an example on how to convert bytes in a human readable form. + + >>> import psutil + >>> psutil.swap_memory() + sswap(total=2097147904L, used=886620160L, free=1210527744L, percent=42.3, sin=1050411008, sout=1906720768) + +Disks +----- + +.. function:: disk_partitions(all=False) + + Return all mounted disk partitions as a list of namedtuples including device, + mount point and filesystem type, similarly to "df" command on UNIX. If *all* + parameter is ``False`` return physical devices only (e.g. hard disks, cd-rom + drives, USB keys) and ignore all others (e.g. memory partitions such as + `/dev/shm <http://www.cyberciti.biz/tips/what-is-devshm-and-its-practical-usage.html>`__). + Namedtuple's **fstype** field is a string which varies depending on the + platform. + On Linux it can be one of the values found in /proc/filesystems (e.g. + ``'ext3'`` for an ext3 hard drive o ``'iso9660'`` for the CD-ROM drive). + On Windows it is determined via + `GetDriveType <http://msdn.microsoft.com/en-us/library/aa364939(v=vs.85).aspx>`__ + and can be either ``"removable"``, ``"fixed"``, ``"remote"``, ``"cdrom"``, + ``"unmounted"`` or ``"ramdisk"``. On OSX and FreeBSD it is retrieved via + `getfsstat(2) <http://www.manpagez.com/man/2/getfsstat/>`__. See + `disk_usage.py <https://github.com/giampaolo/psutil/blob/master/examples/disk_usage.py>`__ + script providing an example usage. + + >>> import psutil + >>> psutil.disk_partitions() + [sdiskpart(device='/dev/sda3', mountpoint='/', fstype='ext4', opts='rw,errors=remount-ro'), + sdiskpart(device='/dev/sda7', mountpoint='/home', fstype='ext4', opts='rw')] + +.. function:: disk_usage(path) + + Return disk usage statistics about the given *path* as a namedtuple including + **total**, **used** and **free** space expressed in bytes, plus the + **percentage** usage. + `OSError <http://docs.python.org/3/library/exceptions.html#OSError>`__ is + raised if *path* does not exist. See + `examples/disk_usage.py <https://github.com/giampaolo/psutil/blob/master/examples/disk_usage.py>`__ + script providing an example usage. Starting from + `Python 3.3 <http://bugs.python.org/issue12442>`__ this is also + available as + `shutil.disk_usage() <http://docs.python.org/3/library/shutil.html#shutil.disk_usage>`__. + See + `disk_usage.py <https://github.com/giampaolo/psutil/blob/master/examples/disk_usage.py>`__ + script providing an example usage. + + >>> import psutil + >>> psutil.disk_usage('/') + sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5) + +.. function:: disk_io_counters(perdisk=False) + + Return system-wide disk I/O statistics as a namedtuple including the + following fields: + + - **read_count**: number of reads + - **write_count**: number of writes + - **read_bytes**: number of bytes read + - **write_bytes**: number of bytes written + - **read_time**: time spent reading from disk (in milliseconds) + - **write_time**: time spent writing to disk (in milliseconds) + + If *perdisk* is ``True`` return the same information for every physical disk + installed on the system as a dictionary with partition names as the keys and + the namedtuple described above as the values. + See `examples/iotop.py <https://github.com/giampaolo/psutil/blob/master/examples/iotop.py>`__ + for an example application. + + >>> import psutil + >>> psutil.disk_io_counters() + sdiskio(read_count=8141, write_count=2431, read_bytes=290203, write_bytes=537676, read_time=5868, write_time=94922) + >>> + >>> psutil.disk_io_counters(perdisk=True) + {'sda1': sdiskio(read_count=920, write_count=1, read_bytes=2933248, write_bytes=512, read_time=6016, write_time=4), + 'sda2': sdiskio(read_count=18707, write_count=8830, read_bytes=6060, write_bytes=3443, read_time=24585, write_time=1572), + 'sdb1': sdiskio(read_count=161, write_count=0, read_bytes=786432, write_bytes=0, read_time=44, write_time=0)} + +Network +------- + +.. function:: net_io_counters(pernic=False) + + Return system-wide network I/O statistics as a namedtuple including the + following attributes: + + - **bytes_sent**: number of bytes sent + - **bytes_recv**: number of bytes received + - **packets_sent**: number of packets sent + - **packets_recv**: number of packets received + - **errin**: total number of errors while receiving + - **errout**: total number of errors while sending + - **dropin**: total number of incoming packets which were dropped + - **dropout**: total number of outgoing packets which were dropped (always 0 + on OSX and BSD) + + If *pernic* is ``True`` return the same information for every network + interface installed on the system as a dictionary with network interface + names as the keys and the namedtuple described above as the values. + See `examples/nettop.py <https://github.com/giampaolo/psutil/blob/master/examples/nettop.py>`__ + for an example application. + + >>> import psutil + >>> psutil.net_io_counters() + snetio(bytes_sent=14508483, bytes_recv=62749361, packets_sent=84311, packets_recv=94888, errin=0, errout=0, dropin=0, dropout=0) + >>> + >>> psutil.net_io_counters(pernic=True) + {'lo': snetio(bytes_sent=547971, bytes_recv=547971, packets_sent=5075, packets_recv=5075, errin=0, errout=0, dropin=0, dropout=0), + 'wlan0': snetio(bytes_sent=13921765, bytes_recv=62162574, packets_sent=79097, packets_recv=89648, errin=0, errout=0, dropin=0, dropout=0)} + +.. function:: net_connections(kind='inet') + + Return system-wide socket connections as a list of namedtuples. + Every namedtuple provides 7 attributes: + + - **fd**: the socket file descriptor, if retrievable, else ``-1``. + If the connection refers to the current process this may be passed to + `socket.fromfd() <http://docs.python.org/library/socket.html#socket.fromfd>`__ + to obtain a usable socket object. + - **family**: the address family, either `AF_INET + <http://docs.python.org//library/socket.html#socket.AF_INET>`__, + `AF_INET6 <http://docs.python.org//library/socket.html#socket.AF_INET6>`__ + or `AF_UNIX <http://docs.python.org//library/socket.html#socket.AF_UNIX>`__. + - **type**: the address type, either `SOCK_STREAM + <http://docs.python.org//library/socket.html#socket.SOCK_STREAM>`__ or + `SOCK_DGRAM + <http://docs.python.org//library/socket.html#socket.SOCK_DGRAM>`__. + - **laddr**: the local address as a ``(ip, port)`` tuple or a ``path`` + in case of AF_UNIX sockets. + - **raddr**: the remote address as a ``(ip, port)`` tuple or an absolute + ``path`` in case of UNIX sockets. + When the remote endpoint is not connected you'll get an empty tuple + (AF_INET*) or ``None`` (AF_UNIX). + On Linux AF_UNIX sockets will always have this set to ``None``. + - **status**: represents the status of a TCP connection. The return value + is one of the :data:`psutil.CONN_* <psutil.CONN_ESTABLISHED>` constants + (a string). + For UDP and UNIX sockets this is always going to be + :const:`psutil.CONN_NONE`. + - **pid**: the PID of the process which opened the socket, if retrievable, + else ``None``. On some platforms (e.g. Linux) the availability of this + field changes depending on process privileges (root is needed). + + The *kind* parameter is a string which filters for connections that fit the + following criteria: + + .. table:: + + +----------------+-----------------------------------------------------+ + | **Kind value** | **Connections using** | + +================+=====================================================+ + | "inet" | IPv4 and IPv6 | + +----------------+-----------------------------------------------------+ + | "inet4" | IPv4 | + +----------------+-----------------------------------------------------+ + | "inet6" | IPv6 | + +----------------+-----------------------------------------------------+ + | "tcp" | TCP | + +----------------+-----------------------------------------------------+ + | "tcp4" | TCP over IPv4 | + +----------------+-----------------------------------------------------+ + | "tcp6" | TCP over IPv6 | + +----------------+-----------------------------------------------------+ + | "udp" | UDP | + +----------------+-----------------------------------------------------+ + | "udp4" | UDP over IPv4 | + +----------------+-----------------------------------------------------+ + | "udp6" | UDP over IPv6 | + +----------------+-----------------------------------------------------+ + | "unix" | UNIX socket (both UDP and TCP protocols) | + +----------------+-----------------------------------------------------+ + | "all" | the sum of all the possible families and protocols | + +----------------+-----------------------------------------------------+ + + On OSX this function requires root privileges. + To get per-process connections use :meth:`Process.connections`. + Also, see + `netstat.py sample script <https://github.com/giampaolo/psutil/blob/master/examples/netstat.py>`__. + Example: + + >>> import psutil + >>> psutil.net_connections() + [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED', pid=1254), + pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING', pid=2987), + pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED', pid=None), + pconn(fd=-1, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT', pid=None) + ...] + + .. note:: (OSX) :class:`psutil.AccessDenied` is always raised unless running + as root (lsof does the same). + .. note:: (Solaris) UNIX sockets are not supported. + + .. versionadded:: 2.1.0 + +.. function:: net_if_addrs() + + Return the addresses associated to each NIC (network interface card) + installed on the system as a dictionary whose keys are the NIC names and + value is a list of namedtuples for each address assigned to the NIC. + Each namedtuple includes 4 fields: + + - **family** + - **address** + - **netmask** + - **broadcast** + + *family* can be either + `AF_INET <http://docs.python.org//library/socket.html#socket.AF_INET>`__, + `AF_INET6 <http://docs.python.org//library/socket.html#socket.AF_INET6>`__ + or :const:`psutil.AF_LINK`, which refers to a MAC address. + *address* is the primary address, *netmask* and *broadcast* may be ``None``. + Example:: + + >>> import psutil + >>> psutil.net_if_addrs() + {'lo': [snic(family=<AddressFamily.AF_INET: 2>, address='127.0.0.1', netmask='255.0.0.0', broadcast='127.0.0.1'), + snic(family=<AddressFamily.AF_INET6: 10>, address='::1', netmask='ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', broadcast=None), + snic(family=<AddressFamily.AF_LINK: 17>, address='00:00:00:00:00:00', netmask=None, broadcast='00:00:00:00:00:00')], + 'wlan0': [snic(family=<AddressFamily.AF_INET: 2>, address='192.168.1.3', netmask='255.255.255.0', broadcast='192.168.1.255'), + snic(family=<AddressFamily.AF_INET6: 10>, address='fe80::c685:8ff:fe45:641%wlan0', netmask='ffff:ffff:ffff:ffff::', broadcast=None), + snic(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff')]} + >>> + + See also `examples/ifconfig.py <https://github.com/giampaolo/psutil/blob/master/examples/ifconfig.py>`__ + for an example application. + + .. note:: if you're interested in others families (e.g. AF_BLUETOOTH) you can + use the more powerful `netifaces <https://pypi.python.org/pypi/netifaces/>`__ + extension. + + .. note:: you can have more than one address of the same family associated + with each interface (that's why dict values are lists). + + *New in 3.0.0* + +.. function:: net_if_stats() + + Return information about each NIC (network interface card) installed on the + system as a dictionary whose keys are the NIC names and value is a namedtuple + with the following fields: + + - **isup** + - **duplex** + - **speed** + - **mtu** + + *isup* is a boolean indicating whether the NIC is up and running, *duplex* + can be either :const:`NIC_DUPLEX_FULL`, :const:`NIC_DUPLEX_HALF` or + :const:`NIC_DUPLEX_UNKNOWN`, *speed* is the NIC speed expressed in mega bits + (MB), if it can't be determined (e.g. 'localhost') it will be set to ``0``, + *mtu* is the maximum transmission unit expressed in bytes. + See also `examples/ifconfig.py <https://github.com/giampaolo/psutil/blob/master/examples/ifconfig.py>`__ + for an example application. + Example: + + >>> import psutil + >>> psutil.net_if_stats() + {'eth0': 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)} + + *New in 3.0.0* + + +Other system info +----------------- + +.. function:: users() + + Return users currently connected on the system as a list of namedtuples + including the following fields: + + - **user**: the name of the user. + - **terminal**: the tty or pseudo-tty associated with the user, if any, + else ``None``. + - **host**: the host name associated with the entry, if any. + - **started**: the creation time as a floating point number expressed in + seconds since the epoch. + + Example:: + + >>> import psutil + >>> psutil.users() + [suser(name='giampaolo', terminal='pts/2', host='localhost', started=1340737536.0), + suser(name='giampaolo', terminal='pts/3', host='localhost', started=1340737792.0)] + +.. function:: boot_time() + + Return the system boot time expressed in seconds since the epoch. + Example: + + .. code-block:: python + + >>> import psutil, datetime + >>> psutil.boot_time() + 1389563460.0 + >>> datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S") + '2014-01-12 22:51:00' + +Processes +========= + +Functions +--------- + +.. function:: pids() + + Return a list of current running PIDs. To iterate over all processes + :func:`process_iter()` should be preferred. + +.. function:: pid_exists(pid) + + Check whether the given PID exists in the current process list. This is + faster than doing ``"pid in psutil.pids()"`` and should be preferred. + +.. function:: process_iter() + + Return an iterator yielding a :class:`Process` class instance for all running + processes on the local machine. + Every instance is only created once and then cached into an internal table + which is updated every time an element is yielded. + Cached :class:`Process` instances are checked for identity so that you're + safe in case a PID has been reused by another process, in which case the + cached instance is updated. + This is should be preferred over :func:`psutil.pids()` for iterating over + processes. + Sorting order in which processes are returned is + based on their PID. Example usage:: + + import psutil + + for proc in psutil.process_iter(): + try: + pinfo = proc.as_dict(attrs=['pid', 'name']) + except psutil.NoSuchProcess: + pass + else: + print(pinfo) + +.. function:: wait_procs(procs, timeout=None, callback=None) + + Convenience function which waits for a list of :class:`Process` instances to + terminate. Return a ``(gone, alive)`` tuple indicating which processes are + gone and which ones are still alive. The *gone* ones will have a new + *returncode* attribute indicating process exit status (it may be ``None``). + ``callback`` is a function which gets called every time a process terminates + (a :class:`Process` instance is passed as callback argument). Function will + return as soon as all processes terminate or when timeout occurs. Tipical use + case is: + + - send SIGTERM to a list of processes + - give them some time to terminate + - send SIGKILL to those ones which are still alive + + Example:: + + import psutil + + def on_terminate(proc): + print("process {} terminated with exit code {}".format(proc, proc.returncode)) + + procs = [...] # a list of Process instances + for p in procs: + p.terminate() + gone, alive = wait_procs(procs, timeout=3, callback=on_terminate) + for p in alive: + p.kill() + +Exceptions +---------- + +.. class:: Error() + + Base exception class. All other exceptions inherit from this one. + +.. class:: NoSuchProcess(pid, name=None, msg=None) + + Raised by :class:`Process` class methods when no process with the given + *pid* is found in the current process list or when a process no longer + exists. "name" is the name the process had before disappearing + and gets set only if :meth:`Process.name()` was previosly called. + +.. class:: ZombieProcess(pid, name=None, ppid=None, msg=None) + + This may be raised by :class:`Process` class methods when querying a zombie + process on UNIX (Windows doesn't have zombie processes). Depending on the + method called the OS may be able to succeed in retrieving the process + information or not. + Note: this is a subclass of :class:`NoSuchProcess` so if you're not + interested in retrieving zombies (e.g. when using :func:`process_iter()`) + you can ignore this exception and just catch :class:`NoSuchProcess`. + + *New in 3.0.0* + +.. class:: AccessDenied(pid=None, name=None, msg=None) + + Raised by :class:`Process` class methods when permission to perform an + action is denied. "name" is the name of the process (may be ``None``). + +.. class:: TimeoutExpired(seconds, pid=None, name=None, msg=None) + + Raised by :meth:`Process.wait` if timeout expires and process is still + alive. + +Process class +------------- + +.. class:: Process(pid=None) + + Represents an OS process with the given *pid*. If *pid* is omitted current + process *pid* (`os.getpid() <http://docs.python.org/library/os.html#os.getpid>`__) + is used. + Raise :class:`NoSuchProcess` if *pid* does not exist. + When accessing methods of this class always be prepared to catch + :class:`NoSuchProcess` and :class:`AccessDenied` exceptions. + `hash() <http://docs.python.org/2/library/functions.html#hash>`__ builtin can + be used against instances of this class in order to identify a process + univocally over time (the hash is determined by mixing process PID + and creation time). As such it can also be used with + `set()s <http://docs.python.org/2/library/stdtypes.html#types-set>`__. + + .. warning:: + + the way this class is bound to a process is uniquely via its **PID**. + That means that if the :class:`Process` instance is old enough and + the PID has been reused by another process in the meantime you might end up + interacting with another process. + The only exceptions for which process identity is pre-emptively checked + (via PID + creation time) and guaranteed are for + :meth:`nice` (set), + :meth:`ionice` (set), + :meth:`cpu_affinity` (set), + :meth:`rlimit` (set), + :meth:`children`, + :meth:`parent`, + :meth:`suspend` + :meth:`resume`, + :meth:`send_signal`, + :meth:`terminate`, and + :meth:`kill` + methods. + To prevent this problem for all other methods you can use + :meth:`is_running()` before querying the process or use + :func:`process_iter()` in case you're iterating over all processes. + + .. attribute:: pid + + The process PID. + + .. method:: ppid() + + The process parent pid. On Windows the return value is cached after first + call. + + .. method:: name() + + The process name. The return value is cached after first call. + + .. method:: exe() + + The process executable as an absolute path. + On some systems this may also be an empty string. + The return value is cached after first call. + + .. method:: cmdline() + + The command line this process has been called with. + + .. method:: create_time() + + The process creation time as a floating point number expressed in seconds + since the epoch, in + `UTC <http://en.wikipedia.org/wiki/Coordinated_universal_time>`__. + The return value is cached after first call. + + >>> import psutil, datetime + >>> p = psutil.Process() + >>> p.create_time() + 1307289803.47 + >>> datetime.datetime.fromtimestamp(p.create_time()).strftime("%Y-%m-%d %H:%M:%S") + '2011-03-05 18:03:52' + + .. method:: as_dict(attrs=None, ad_value=None) + + Utility method returning process information as a hashable dictionary. + If *attrs* is specified it must be a list of strings reflecting available + :class:`Process` class's attribute names (e.g. ``['cpu_times', 'name']``) + else all public (read only) attributes are assumed. *ad_value* is the + value which gets assigned to a dict key in case :class:`AccessDenied` + or :class:`ZombieProcess` exception is raised when retrieving that + particular process information. + + >>> import psutil + >>> p = psutil.Process() + >>> p.as_dict(attrs=['pid', 'name', 'username']) + {'username': 'giampaolo', 'pid': 12366, 'name': 'python'} + + .. versionchanged:: 3.0.0 *ad_value* is used also when incurring into + :class:`ZombieProcess` exception, not only :class:`AccessDenied` + + .. method:: parent() + + Utility method which returns the parent process as a :class:`Process` + object pre-emptively checking whether PID has been reused. If no parent + PID is known return ``None``. + + .. method:: status() + + The current process status as a string. The returned string is one of the + :data:`psutil.STATUS_*<psutil.STATUS_RUNNING>` constants. + + .. method:: cwd() + + The process current working directory as an absolute path. + + .. method:: username() + + The name of the user that owns the process. On UNIX this is calculated by + using real process uid. + + .. method:: uids() + + The **real**, **effective** and **saved** user ids of this process as a + namedtuple. This is the same as + `os.getresuid() <http://docs.python.org//library/os.html#os.getresuid>`__ + but can be used for every process PID. + + Availability: UNIX + + .. method:: gids() + + The **real**, **effective** and **saved** group ids of this process as a + namedtuple. This is the same as + `os.getresgid() <http://docs.python.org//library/os.html#os.getresgid>`__ + but can be used for every process PID. + + Availability: UNIX + + .. method:: terminal() + + The terminal associated with this process, if any, else ``None``. This is + similar to "tty" command but can be used for every process PID. + + Availability: UNIX + + .. method:: nice(value=None) + + Get or set process + `niceness <blogs.techrepublic.com.com/opensource/?p=140>`__ (priority). + On UNIX this is a number which usually goes from ``-20`` to ``20``. + The higher the nice value, the lower the priority of the process. + + >>> import psutil + >>> p = psutil.Process() + >>> p.nice(10) # set + >>> p.nice() # get + 10 + >>> + + Starting from `Python 3.3 <http://bugs.python.org/issue10784>`__ this + functionality is also available as + `os.getpriority() <http://docs.python.org/3/library/os.html#os.getpriority>`__ + and + `os.setpriority() <http://docs.python.org/3/library/os.html#os.setpriority>`__ + (UNIX only). + + On Windows this is available as well by using + `GetPriorityClass <http://msdn.microsoft.com/en-us/library/ms683211(v=vs.85).aspx>`__ + and `SetPriorityClass <http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx>`__ + and *value* is one of the + :data:`psutil.*_PRIORITY_CLASS <psutil.ABOVE_NORMAL_PRIORITY_CLASS>` + constants. + Example which increases process priority on Windows: + + >>> p.nice(psutil.HIGH_PRIORITY_CLASS) + + .. method:: ionice(ioclass=None, value=None) + + Get or set + `process I/O niceness <http://friedcpu.wordpress.com/2007/07/17/why-arent-you-using-ionice-yet/>`__ (priority). + On Linux *ioclass* is one of the + :data:`psutil.IOPRIO_CLASS_*<psutil.IOPRIO_CLASS_NONE>` constants. + *value* is a number which goes from ``0`` to ``7``. The higher the value, + the lower the I/O priority of the process. On Windows only *ioclass* is + used and it can be set to ``2`` (normal), ``1`` (low) or ``0`` (very low). + The example below sets IDLE priority class for the current process, + meaning it will only get I/O time when no other process needs the disk: + + >>> import psutil + >>> p = psutil.Process() + >>> p.ionice(psutil.IOPRIO_CLASS_IDLE) # set + >>> p.ionice() # get + pionice(ioclass=<IOPriority.IOPRIO_CLASS_IDLE: 3>, value=0) + >>> + + On Windows only *ioclass* is used and it can be set to ``2`` (normal), + ``1`` (low) or ``0`` (very low). + + Availability: Linux and Windows > Vista + + .. versionchanged:: 3.0.0 on >= Python 3.4 the returned ``ioclass`` + constant is an `enum <https://docs.python.org/3/library/enum.html#module-enum>`__ + instead of a plain integer. + + .. method:: rlimit(resource, limits=None) + + Get or set process resource limits (see + `man prlimit <http://linux.die.net/man/2/prlimit>`__). *resource* is one of + the :data:`psutil.RLIMIT_* <psutil.RLIMIT_INFINITY>` constants. + *limits* is a ``(soft, hard)`` tuple. + This is the same as `resource.getrlimit() <http://docs.python.org/library/resource.html#resource.getrlimit>`__ + and `resource.setrlimit() <http://docs.python.org/library/resource.html#resource.setrlimit>`__ + but can be used for every process PID and only on Linux. + Example: + + >>> import psutil + >>> p = psutil.Process() + >>> # process may open no more than 128 file descriptors + >>> p.rlimit(psutil.RLIMIT_NOFILE, (128, 128)) + >>> # process may create files no bigger than 1024 bytes + >>> p.rlimit(psutil.RLIMIT_FSIZE, (1024, 1024)) + >>> # get + >>> p.rlimit(psutil.RLIMIT_FSIZE) + (1024, 1024) + >>> + + Availability: Linux + + .. method:: io_counters() + + Return process I/O statistics as a namedtuple including the number of read + and write operations performed by the process and the amount of bytes read + and written. For Linux refer to + `/proc filesysem documentation <https://www.kernel.org/doc/Documentation/filesystems/proc.txt>`__. + On BSD there's apparently no way to retrieve bytes counters, hence ``-1`` + is returned for **read_bytes** and **write_bytes** fields. OSX is not + supported. + + >>> import psutil + >>> p = psutil.Process() + >>> p.io_counters() + pio(read_count=454556, write_count=3456, read_bytes=110592, write_bytes=0) + + Availability: all platforms except OSX and Solaris + + .. method:: num_ctx_switches() + + The number voluntary and involuntary context switches performed by + this process. + + .. method:: num_fds() + + The number of file descriptors used by this process. + + Availability: UNIX + + .. method:: num_handles() + + The number of handles used by this process. + + Availability: Windows + + .. method:: num_threads() + + The number of threads currently used by this process. + + .. method:: threads() + + Return threads opened by process as a list of namedtuples including thread + id and thread CPU times (user/system). + + .. method:: cpu_times() + + Return a tuple whose values are process CPU **user** and **system** + times which means the amount of time expressed in seconds that a process + has spent in + `user / system mode <http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1>`__. + This is similar to + `os.times() <http://docs.python.org//library/os.html#os.times>`__ + but can be used for every process PID. + + .. method:: cpu_percent(interval=None) + + Return a float representing the process CPU utilization as a percentage. + When *interval* is > ``0.0`` compares process times to system CPU times + elapsed before and after the interval (blocking). When interval is ``0.0`` + or ``None`` compares process times to system CPU times elapsed since last + call, returning immediately. That means the first time this is called it + will return a meaningless ``0.0`` value which you are supposed to ignore. + In this case is recommended for accuracy that this function be called a + second time with at least ``0.1`` seconds between calls. Example: + + >>> import psutil + >>> p = psutil.Process() + >>> + >>> # blocking + >>> p.cpu_percent(interval=1) + 2.0 + >>> # non-blocking (percentage since last call) + >>> p.cpu_percent(interval=None) + 2.9 + >>> + + .. note:: + a percentage > 100 is legitimate as it can result from a process with + multiple threads running on different CPU cores. + + .. warning:: + the first time this method is called with interval = ``0.0`` or + ``None`` it will return a meaningless ``0.0`` value which you are + supposed to ignore. + + .. method:: cpu_affinity(cpus=None) + + Get or set process current + `CPU affinity <http://www.linuxjournal.com/article/6799?page=0,0>`__. + CPU affinity consists in telling the OS to run a certain process on a + limited set of CPUs only. The number of eligible CPUs can be obtained with + ``list(range(psutil.cpu_count()))``. On set raises ``ValueError`` in case + an invalid CPU number is specified. + + >>> import psutil + >>> psutil.cpu_count() + 4 + >>> p = psutil.Process() + >>> p.cpu_affinity() # get + [0, 1, 2, 3] + >>> p.cpu_affinity([0]) # set; from now on, process will run on CPU #0 only + >>> p.cpu_affinity() + [0] + >>> + >>> # reset affinity against all CPUs + >>> all_cpus = list(range(psutil.cpu_count())) + >>> p.cpu_affinity(all_cpus) + >>> + + Availability: Linux, Windows, BSD + + .. versionchanged:: 2.2.0 added support for FreeBSD + + .. method:: memory_info() + + Return a tuple representing RSS (Resident Set Size) and VMS (Virtual + Memory Size) in bytes. On UNIX *rss* and *vms* are the same values shown + by ps. On Windows *rss* and *vms* refer to "Mem Usage" and "VM Size" + columns of taskmgr.exe. For more detailed memory stats use + :meth:`memory_info_ex`. + + .. method:: memory_info_ex() + + Return a namedtuple with variable fields depending on the platform + representing extended memory information about the process. + All numbers are expressed in bytes. + + +--------+---------+-------+-------+--------------------+ + | Linux | OSX | BSD | SunOS | Windows | + +========+=========+=======+=======+====================+ + | rss | rss | rss | rss | num_page_faults | + +--------+---------+-------+-------+--------------------+ + | vms | vms | vms | vms | peak_wset | + +--------+---------+-------+-------+--------------------+ + | shared | pfaults | text | | wset | + +--------+---------+-------+-------+--------------------+ + | text | pageins | data | | peak_paged_pool | + +--------+---------+-------+-------+--------------------+ + | lib | | stack | | paged_pool | + +--------+---------+-------+-------+--------------------+ + | data | | | | peak_nonpaged_pool | + +--------+---------+-------+-------+--------------------+ + | dirty | | | | nonpaged_pool | + +--------+---------+-------+-------+--------------------+ + | | | | | pagefile | + +--------+---------+-------+-------+--------------------+ + | | | | | peak_pagefile | + +--------+---------+-------+-------+--------------------+ + | | | | | private | + +--------+---------+-------+-------+--------------------+ + + Windows metrics are extracted from + `PROCESS_MEMORY_COUNTERS_EX <http://msdn.microsoft.com/en-us/library/windows/desktop/ms684874(v=vs.85).aspx>`__ structure. + Example on Linux: + + >>> import psutil + >>> p = psutil.Process() + >>> p.memory_info_ex() + pextmem(rss=15491072, vms=84025344, shared=5206016, text=2555904, lib=0, data=9891840, dirty=0) + + .. method:: memory_percent() + + Compare physical system memory to process resident memory (RSS) and + calculate process memory utilization as a percentage. + + .. method:: memory_maps(grouped=True) + + Return process's mapped memory regions as a list of namedtuples whose + fields are variable depending on the platform. As such, portable + applications should rely on namedtuple's `path` and `rss` fields only. + This method is useful to obtain a detailed representation of process + memory usage as explained + `here <http://bmaurer.blogspot.it/2006/03/memory-usage-with-smaps.html>`__. + If *grouped* is ``True`` the mapped regions with the same *path* are + grouped together and the different memory fields are summed. If *grouped* + is ``False`` every mapped region is shown as a single entity and the + namedtuple will also include the mapped region's address space (*addr*) + and permission set (*perms*). + See `examples/pmap.py <https://github.com/giampaolo/psutil/blob/master/examples/pmap.py>`__ + for an example application. + + >>> import psutil + >>> p = psutil.Process() + >>> p.memory_maps() + [pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=16384, anonymous=8192, swap=0), + pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=6384, anonymous=15, swap=0), + pmmap_grouped(path='/lib/x8664-linux-gnu/libcrypto.so.0.1', rss=34124, anonymous=1245, swap=0), + pmmap_grouped(path='[heap]', rss=54653, anonymous=8192, swap=0), + pmmap_grouped(path='[stack]', rss=1542, anonymous=166, swap=0), + ...] + >>> + + .. method:: children(recursive=False) + + Return the children of this process as a list of :Class:`Process` objects, + pre-emptively checking whether PID has been reused. If recursive is `True` + return all the parent descendants. + Example assuming *A == this process*: + :: + + A ─┐ + │ + ├─ B (child) ─┐ + │ └─ X (grandchild) ─┐ + │ └─ Y (great grandchild) + ├─ C (child) + └─ D (child) + + >>> p.children() + B, C, D + >>> p.children(recursive=True) + B, X, Y, C, D + + Note that in the example above if process X disappears process Y won't be + returned either as the reference to process A is lost. + + .. method:: open_files() + + Return regular files opened by process as a list of namedtuples including + the absolute file name and the file descriptor number (on Windows this is + always ``-1``). Example: + + >>> import psutil + >>> f = open('file.ext', 'w') + >>> p = psutil.Process() + >>> p.open_files() + [popenfile(path='/home/giampaolo/svn/psutil/file.ext', fd=3)] + + .. warning:: + on Windows this is not fully reliable as due to some limitations of the + Windows API the underlying implementation may hang when retrieving + certain file handles. + In order to work around that psutil on Windows Vista (and higher) spawns + a thread and kills it if it's not responding after 100ms. + That implies that on Windows this method is not guaranteed to enumerate + all regular file handles (see full discusion + `here <https://github.com/giampaolo/psutil/pull/597>`_). + + .. warning:: + on FreeBSD this method can return files with a 'null' path (see + `issue 595 <https://github.com/giampaolo/psutil/pull/595>`_). + + .. versionchanged:: 3.1.0 no longer hangs on Windows. + + .. method:: connections(kind="inet") + + Return socket connections opened by process as a list of namedtuples. + To get system-wide connections use :func:`psutil.net_connections()`. + Every namedtuple provides 6 attributes: + + - **fd**: the socket file descriptor. This can be passed to + `socket.fromfd() <http://docs.python.org/library/socket.html#socket.fromfd>`__ + to obtain a usable socket object. + This is only available on UNIX; on Windows ``-1`` is always returned. + - **family**: the address family, either `AF_INET + <http://docs.python.org//library/socket.html#socket.AF_INET>`__, + `AF_INET6 <http://docs.python.org//library/socket.html#socket.AF_INET6>`__ + or `AF_UNIX <http://docs.python.org//library/socket.html#socket.AF_UNIX>`__. + - **type**: the address type, either `SOCK_STREAM + <http://docs.python.org//library/socket.html#socket.SOCK_STREAM>`__ or + `SOCK_DGRAM + <http://docs.python.org//library/socket.html#socket.SOCK_DGRAM>`__. + - **laddr**: the local address as a ``(ip, port)`` tuple or a ``path`` + in case of AF_UNIX sockets. + - **raddr**: the remote address as a ``(ip, port)`` tuple or an absolute + ``path`` in case of UNIX sockets. + When the remote endpoint is not connected you'll get an empty tuple + (AF_INET) or ``None`` (AF_UNIX). + On Linux AF_UNIX sockets will always have this set to ``None``. + - **status**: represents the status of a TCP connection. The return value + is one of the :data:`psutil.CONN_* <psutil.CONN_ESTABLISHED>` constants. + For UDP and UNIX sockets this is always going to be + :const:`psutil.CONN_NONE`. + + The *kind* parameter is a string which filters for connections that fit the + following criteria: + + .. table:: + + +----------------+-----------------------------------------------------+ + | **Kind value** | **Connections using** | + +================+=====================================================+ + | "inet" | IPv4 and IPv6 | + +----------------+-----------------------------------------------------+ + | "inet4" | IPv4 | + +----------------+-----------------------------------------------------+ + | "inet6" | IPv6 | + +----------------+-----------------------------------------------------+ + | "tcp" | TCP | + +----------------+-----------------------------------------------------+ + | "tcp4" | TCP over IPv4 | + +----------------+-----------------------------------------------------+ + | "tcp6" | TCP over IPv6 | + +----------------+-----------------------------------------------------+ + | "udp" | UDP | + +----------------+-----------------------------------------------------+ + | "udp4" | UDP over IPv4 | + +----------------+-----------------------------------------------------+ + | "udp6" | UDP over IPv6 | + +----------------+-----------------------------------------------------+ + | "unix" | UNIX socket (both UDP and TCP protocols) | + +----------------+-----------------------------------------------------+ + | "all" | the sum of all the possible families and protocols | + +----------------+-----------------------------------------------------+ + + Example: + + >>> import psutil + >>> p = psutil.Process(1694) + >>> p.name() + 'firefox' + >>> p.connections() + [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 48776), raddr=('93.186.135.91', 80), status='ESTABLISHED'), + pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 43761), raddr=('72.14.234.100', 80), status='CLOSING'), + pconn(fd=119, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 60759), raddr=('72.14.234.104', 80), status='ESTABLISHED'), + pconn(fd=123, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=('10.0.0.1', 51314), raddr=('72.14.234.83', 443), status='SYN_SENT')] + + .. method:: is_running() + + Return whether the current process is running in the current process list. + This is reliable also in case the process is gone and its PID reused by + another process, therefore it must be preferred over doing + ``psutil.pid_exists(p.pid)``. + + .. note:: + this will return ``True`` also if the process is a zombie + (``p.status() == psutil.STATUS_ZOMBIE``). + + .. method:: send_signal(signal) + + Send a signal to process (see + `signal module <http://docs.python.org//library/signal.html>`__ + constants) pre-emptively checking whether PID has been reused. + This is the same as ``os.kill(pid, sig)``. + On Windows only **SIGTERM** is valid and is treated as an alias for + :meth:`kill()`. + + .. method:: suspend() + + Suspend process execution with **SIGSTOP** signal pre-emptively checking + whether PID has been reused. + On UNIX this is the same as ``os.kill(pid, signal.SIGSTOP)``. + On Windows this is done by suspending all process threads execution. + + .. method:: resume() + + Resume process execution with **SIGCONT** signal pre-emptively checking + whether PID has been reused. + On UNIX this is the same as ``os.kill(pid, signal.SIGCONT)``. + On Windows this is done by resuming all process threads execution. + + .. method:: terminate() + + Terminate the process with **SIGTERM** signal pre-emptively checking + whether PID has been reused. + On UNIX this is the same as ``os.kill(pid, signal.SIGTERM)``. + On Windows this is an alias for :meth:`kill`. + + .. method:: kill() + + Kill the current process by using **SIGKILL** signal pre-emptively + checking whether PID has been reused. + On UNIX this is the same as ``os.kill(pid, signal.SIGKILL)``. + On Windows this is done by using + `TerminateProcess <http://msdn.microsoft.com/en-us/library/windows/desktop/ms686714(v=vs.85).aspx>`__. + + .. method:: wait(timeout=None) + + Wait for process termination and if the process is a children of the + current one also return the exit code, else ``None``. On Windows there's + no such limitation (exit code is always returned). If the process is + already terminated immediately return ``None`` instead of raising + :class:`NoSuchProcess`. If *timeout* is specified and process is still + alive raise :class:`TimeoutExpired` exception. It can also be used in a + non-blocking fashion by specifying ``timeout=0`` in which case it will + either return immediately or raise :class:`TimeoutExpired`. + To wait for multiple processes use :func:`psutil.wait_procs()`. + + +Popen class +----------- + +.. class:: Popen(*args, **kwargs) + + A more convenient interface to stdlib + `subprocess.Popen <http://docs.python.org/library/subprocess.html#subprocess.Popen>`__. + It starts a sub process and deals with it exactly as when using + `subprocess.Popen <http://docs.python.org/library/subprocess.html#subprocess.Popen>`__ + but in addition it also provides all the methods of + :class:`psutil.Process` class in a single interface. + For method names common to both classes such as + :meth:`send_signal() <psutil.Process.send_signal()>`, + :meth:`terminate() <psutil.Process.terminate()>` and + :meth:`kill() <psutil.Process.kill()>` + :class:`psutil.Process` implementation takes precedence. + For a complete documentation refer to + `subprocess module documentation <http://docs.python.org/library/subprocess.html>`__. + + .. note:: + + Unlike `subprocess.Popen <http://docs.python.org/library/subprocess.html#subprocess.Popen>`__ + this class pre-emptively checks wheter PID has been reused on + :meth:`send_signal() <psutil.Process.send_signal()>`, + :meth:`terminate() <psutil.Process.terminate()>` and + :meth:`kill() <psutil.Process.kill()>` + so that you can't accidentally terminate another process, fixing + http://bugs.python.org/issue6973. + + >>> 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 + >>> + +Constants +========= + +.. _const-pstatus: +.. data:: STATUS_RUNNING + STATUS_SLEEPING + STATUS_DISK_SLEEP + STATUS_STOPPED + STATUS_TRACING_STOP + STATUS_ZOMBIE + STATUS_DEAD + STATUS_WAKE_KILL + STATUS_WAKING + STATUS_IDLE + STATUS_LOCKED + STATUS_WAITING + + A set of strings representing the status of a process. + Returned by :meth:`psutil.Process.status()`. + +.. _const-conn: +.. data:: 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_NONE + CONN_DELETE_TCB (Windows) + CONN_IDLE (Solaris) + CONN_BOUND (Solaris) + + A set of strings representing the status of a TCP connection. + Returned by :meth:`psutil.Process.connections()` (`status` field). + +.. _const-prio: +.. data:: ABOVE_NORMAL_PRIORITY_CLASS + BELOW_NORMAL_PRIORITY_CLASS + HIGH_PRIORITY_CLASS + IDLE_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS + REALTIME_PRIORITY_CLASS + + A set of integers representing the priority of a process on Windows (see + `MSDN documentation <http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx>`__). + They can be used in conjunction with + :meth:`psutil.Process.nice()` to get or set process priority. + + Availability: Windows + + .. versionchanged:: 3.0.0 on Python >= 3.4 these constants are + `enums <https://docs.python.org/3/library/enum.html#module-enum>`__ + instead of a plain integer. + +.. _const-ioprio: +.. data:: IOPRIO_CLASS_NONE + IOPRIO_CLASS_RT + IOPRIO_CLASS_BE + IOPRIO_CLASS_IDLE + + 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. + *IOPRIO_CLASS_NONE* and *IOPRIO_CLASS_BE* (best effort) is the default for + any process that hasn't set a specific I/O priority. + *IOPRIO_CLASS_RT* (real time) means the process is given first access to the + disk, regardless of what else is going on in the system. + *IOPRIO_CLASS_IDLE* means the process will get I/O time when no-one else + needs the disk. + For further information refer to manuals of + `ionice <http://linux.die.net/man/1/ionice>`__ + command line utility or + `ioprio_get <http://linux.die.net/man/2/ioprio_get>`__ + system call. + + Availability: Linux + + .. versionchanged:: 3.0.0 on Python >= 3.4 thse constants are + `enums <https://docs.python.org/3/library/enum.html#module-enum>`__ + instead of a plain integer. + +.. _const-rlimit: +.. data:: RLIMIT_INFINITY + RLIMIT_AS + RLIMIT_CORE + RLIMIT_CPU + RLIMIT_DATA + RLIMIT_FSIZE + RLIMIT_LOCKS + RLIMIT_MEMLOCK + RLIMIT_MSGQUEUE + RLIMIT_NICE + RLIMIT_NOFILE + RLIMIT_NPROC + RLIMIT_RSS + RLIMIT_RTPRIO + RLIMIT_RTTIME + RLIMIT_RTPRIO + RLIMIT_SIGPENDING + RLIMIT_STACK + + Constants used for getting and setting process resource limits to be used in + conjunction with :meth:`psutil.Process.rlimit()`. See + `man prlimit <http://linux.die.net/man/2/prlimit>`__ for futher information. + + Availability: Linux + +.. _const-aflink: +.. data:: AF_LINK + + Constant which identifies a MAC address associated with a network interface. + To be used in conjunction with :func:`psutil.net_if_addrs()`. + + *New in 3.0.0* + +.. _const-duplex: +.. data:: NIC_DUPLEX_FULL + NIC_DUPLEX_HALF + NIC_DUPLEX_UNKNOWN + + Constants which identifies whether a NIC (network interface card) has full or + half mode speed. NIC_DUPLEX_FULL means the NIC is able to send and receive + data (files) simultaneously, NIC_DUPLEX_FULL means the NIC can either send or + receive data at a time. + To be used in conjunction with :func:`psutil.net_if_stats()`. + + *New in 3.0.0* + +Development guide +================= + +If you plan on hacking on psutil (e.g. want to add a new feature or fix a bug) +take a look at the +`development guide <https://github.com/giampaolo/psutil/blob/master/DEVGUIDE.rst>`_. diff --git a/python/psutil/docs/make.bat b/python/psutil/docs/make.bat new file mode 100644 index 0000000000..9bc67515c6 --- /dev/null +++ b/python/psutil/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^<target^>` where ^<target^> is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\psutil.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\psutil.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/python/psutil/docs/xxx b/python/psutil/docs/xxx new file mode 100644 index 0000000000..b78d53f2d6 --- /dev/null +++ b/python/psutil/docs/xxx @@ -0,0 +1,11 @@ +cpu 1974613 1749 485728 6305758 80280 15 5924 0 0 0 + +cpu0 519156 374 132999 5977865 72925 10 1458 0 0 0 + +cpu1 524667 401 125931 108960 2110 4 2214 0 0 0 + +cpu2 462286 520 117046 109514 2666 0 828 0 0 0 + +cpu3 468502 453 109750 109418 2578 0 1424 0 0 0 + + diff --git a/python/psutil/examples/disk_usage.py b/python/psutil/examples/disk_usage.py new file mode 100755 index 0000000000..d8600a8c47 --- /dev/null +++ b/python/psutil/examples/disk_usage.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# 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. + +""" +List all mounted disk partitions a-la "df -h" command. + +$ python examples/disk_usage.py +Device Total Used Free Use % Type Mount +/dev/sdb3 18.9G 14.7G 3.3G 77% ext4 / +/dev/sda6 345.9G 83.8G 244.5G 24% ext4 /home +/dev/sda1 296.0M 43.1M 252.9M 14% vfat /boot/efi +/dev/sda2 600.0M 312.4M 287.6M 52% fuseblk /media/Recovery +""" + +import sys +import os +import psutil + + +def bytes2human(n): + # http://code.activestate.com/recipes/578019 + # >>> bytes2human(10000) + # '9.8K' + # >>> bytes2human(100001221) + # '95.4M' + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.1f%s' % (value, s) + return "%sB" % n + + +def main(): + templ = "%-17s %8s %8s %8s %5s%% %9s %s" + print(templ % ("Device", "Total", "Used", "Free", "Use ", "Type", + "Mount")) + for part in psutil.disk_partitions(all=False): + if os.name == 'nt': + if 'cdrom' in part.opts or part.fstype == '': + # skip cd-rom drives with no disk in it; they may raise + # ENOENT, pop-up a Windows GUI error for a non-ready + # partition or just hang. + continue + usage = psutil.disk_usage(part.mountpoint) + print(templ % ( + part.device, + bytes2human(usage.total), + bytes2human(usage.used), + bytes2human(usage.free), + int(usage.percent), + part.fstype, + part.mountpoint)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/python/psutil/examples/free.py b/python/psutil/examples/free.py new file mode 100755 index 0000000000..913ca58a4c --- /dev/null +++ b/python/psutil/examples/free.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of 'free' cmdline utility. + +$ python examples/free.py + total used free shared buffers cache +Mem: 10125520 8625996 1499524 0 349500 3307836 +Swap: 0 0 0 +""" + +import psutil + + +def main(): + virt = psutil.virtual_memory() + swap = psutil.swap_memory() + templ = "%-7s %10s %10s %10s %10s %10s %10s" + print(templ % ('', 'total', 'used', 'free', 'shared', 'buffers', 'cache')) + print(templ % ( + 'Mem:', + int(virt.total / 1024), + int(virt.used / 1024), + int(virt.free / 1024), + int(getattr(virt, 'shared', 0) / 1024), + int(getattr(virt, 'buffers', 0) / 1024), + int(getattr(virt, 'cached', 0) / 1024))) + print(templ % ( + 'Swap:', int(swap.total / 1024), + int(swap.used / 1024), + int(swap.free / 1024), + '', + '', + '')) + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/ifconfig.py b/python/psutil/examples/ifconfig.py new file mode 100644 index 0000000000..e7a436cc0a --- /dev/null +++ b/python/psutil/examples/ifconfig.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of 'ifconfig' on UNIX. + +$ python examples/ifconfig.py +lo (speed=0MB, duplex=?, mtu=65536, up=yes): + IPv4 address : 127.0.0.1 + broadcast : 127.0.0.1 + netmask : 255.0.0.0 + IPv6 address : ::1 + netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + MAC address : 00:00:00:00:00:00 + broadcast : 00:00:00:00:00:00 + +wlan0 (speed=0MB, duplex=?, mtu=1500, up=yes): + IPv4 address : 10.0.3.1 + broadcast : 10.0.3.255 + netmask : 255.255.255.0 + IPv6 address : fe80::3005:adff:fe31:8698 + netmask : ffff:ffff:ffff:ffff:: + MAC address : 32:05:ad:31:86:98 + broadcast : ff:ff:ff:ff:ff:ff + +eth0 (speed=100MB, duplex=full, mtu=1500, up=yes): + IPv4 address : 192.168.1.2 + broadcast : 192.168.1.255 + netmask : 255.255.255.0 + IPv6 address : fe80::c685:8ff:fe45:641 + netmask : ffff:ffff:ffff:ffff:: + MAC address : c4:85:08:45:06:41 + broadcast : ff:ff:ff:ff:ff:ff +""" + +from __future__ import print_function +import socket + +import psutil + + +af_map = { + socket.AF_INET: 'IPv4', + socket.AF_INET6: 'IPv6', + psutil.AF_LINK: 'MAC', +} + +duplex_map = { + psutil.NIC_DUPLEX_FULL: "full", + psutil.NIC_DUPLEX_HALF: "half", + psutil.NIC_DUPLEX_UNKNOWN: "?", +} + + +def main(): + stats = psutil.net_if_stats() + for nic, addrs in psutil.net_if_addrs().items(): + if nic in stats: + print("%s (speed=%sMB, duplex=%s, mtu=%s, up=%s):" % ( + nic, stats[nic].speed, duplex_map[stats[nic].duplex], + stats[nic].mtu, "yes" if stats[nic].isup else "no")) + else: + print("%s:" % (nic)) + for addr in addrs: + print(" %-8s" % af_map.get(addr.family, addr.family), end="") + print(" address : %s" % addr.address) + if addr.broadcast: + print(" broadcast : %s" % addr.broadcast) + if addr.netmask: + print(" netmask : %s" % addr.netmask) + print("") + + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/iotop.py b/python/psutil/examples/iotop.py new file mode 100755 index 0000000000..16ac7fbf61 --- /dev/null +++ b/python/psutil/examples/iotop.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of iotop (http://guichaz.free.fr/iotop/) showing real time +disk I/O statistics. + +It works on Linux only (FreeBSD and OSX are missing support for IO +counters). +It doesn't work on Windows as curses module is required. + +Example output: + +$ python examples/iotop.py +Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s +PID USER DISK READ DISK WRITE COMMAND +13155 giampao 0.00 B/s 428.00 K/s /usr/bin/google-chrome-beta +3260 giampao 0.00 B/s 0.00 B/s bash +3779 giampao 0.00 B/s 0.00 B/s gnome-session --session=ubuntu +3830 giampao 0.00 B/s 0.00 B/s /usr/bin/dbus-launch +3831 giampao 0.00 B/s 0.00 B/s //bin/dbus-daemon --fork --print-pid 5 +3841 giampao 0.00 B/s 0.00 B/s /usr/lib/at-spi-bus-launcher +3845 giampao 0.00 B/s 0.00 B/s /bin/dbus-daemon +3848 giampao 0.00 B/s 0.00 B/s /usr/lib/at-spi2-core/at-spi2-registryd +3862 giampao 0.00 B/s 0.00 B/s /usr/lib/gnome-settings-daemon + +Author: Giampaolo Rodola' <g.rodola@gmail.com> +""" + +import atexit +import time +import sys +try: + import curses +except ImportError: + sys.exit('platform not supported') + +import psutil + + +# --- curses stuff +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + +win = curses.initscr() +atexit.register(tear_down) +curses.endwin() +lineno = 0 + + +def print_line(line, highlight=False): + """A thin wrapper around curses's addstr().""" + global lineno + try: + if highlight: + line += " " * (win.getmaxyx()[1] - len(line)) + win.addstr(lineno, 0, line, curses.A_REVERSE) + else: + win.addstr(lineno, 0, line, 0) + except curses.error: + lineno = 0 + win.refresh() + raise + else: + lineno += 1 +# --- /curses stuff + + +def bytes2human(n): + """ + >>> bytes2human(10000) + '9.8 K/s' + >>> bytes2human(100001221) + '95.4 M/s' + """ + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.2f %s/s' % (value, s) + return '%.2f B/s' % (n) + + +def poll(interval): + """Calculate IO usage by comparing IO statics before and + after the interval. + Return a tuple including all currently running processes + sorted by IO activity and total disks I/O activity. + """ + # first get a list of all processes and disk io counters + procs = [p for p in psutil.process_iter()] + for p in procs[:]: + try: + p._before = p.io_counters() + except psutil.Error: + procs.remove(p) + continue + disks_before = psutil.disk_io_counters() + + # sleep some time + time.sleep(interval) + + # then retrieve the same info again + for p in procs[:]: + try: + p._after = p.io_counters() + p._cmdline = ' '.join(p.cmdline()) + if not p._cmdline: + p._cmdline = p.name() + p._username = p.username() + except (psutil.NoSuchProcess, psutil.ZombieProcess): + procs.remove(p) + disks_after = psutil.disk_io_counters() + + # finally calculate results by comparing data before and + # after the interval + for p in procs: + p._read_per_sec = p._after.read_bytes - p._before.read_bytes + p._write_per_sec = p._after.write_bytes - p._before.write_bytes + p._total = p._read_per_sec + p._write_per_sec + + disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes + disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes + + # sort processes by total disk IO so that the more intensive + # ones get listed first + processes = sorted(procs, key=lambda p: p._total, reverse=True) + + return (processes, disks_read_per_sec, disks_write_per_sec) + + +def refresh_window(procs, disks_read, disks_write): + """Print results on screen by using curses.""" + curses.endwin() + templ = "%-5s %-7s %11s %11s %s" + win.erase() + + disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \ + % (bytes2human(disks_read), bytes2human(disks_write)) + print_line(disks_tot) + + header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND") + print_line(header, highlight=True) + + for p in procs: + line = templ % ( + p.pid, + p._username[:7], + bytes2human(p._read_per_sec), + bytes2human(p._write_per_sec), + p._cmdline) + try: + print_line(line) + except curses.error: + break + win.refresh() + + +def main(): + try: + interval = 0 + while True: + args = poll(interval) + refresh_window(*args) + interval = 1 + except (KeyboardInterrupt, SystemExit): + pass + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/killall.py b/python/psutil/examples/killall.py new file mode 100755 index 0000000000..b548e7bc57 --- /dev/null +++ b/python/psutil/examples/killall.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +# 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. + +""" +Kill a process by name. +""" + +import os +import sys +import psutil + + +def main(): + if len(sys.argv) != 2: + sys.exit('usage: %s name' % __file__) + else: + NAME = sys.argv[1] + + killed = [] + for proc in psutil.process_iter(): + if proc.name() == NAME and proc.pid != os.getpid(): + proc.kill() + killed.append(proc.pid) + if not killed: + sys.exit('%s: no process found' % NAME) + else: + sys.exit(0) + +sys.exit(main()) diff --git a/python/psutil/examples/meminfo.py b/python/psutil/examples/meminfo.py new file mode 100755 index 0000000000..c463a3de4b --- /dev/null +++ b/python/psutil/examples/meminfo.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# 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. + +""" +Print system memory information. + +$ python examples/meminfo.py +MEMORY +------ +Total : 9.7G +Available : 4.9G +Percent : 49.0 +Used : 8.2G +Free : 1.4G +Active : 5.6G +Inactive : 2.1G +Buffers : 341.2M +Cached : 3.2G + +SWAP +---- +Total : 0B +Used : 0B +Free : 0B +Percent : 0.0 +Sin : 0B +Sout : 0B +""" + +import psutil + + +def bytes2human(n): + # http://code.activestate.com/recipes/578019 + # >>> bytes2human(10000) + # '9.8K' + # >>> bytes2human(100001221) + # '95.4M' + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.1f%s' % (value, s) + return "%sB" % n + + +def pprint_ntuple(nt): + for name in nt._fields: + value = getattr(nt, name) + if name != 'percent': + value = bytes2human(value) + print('%-10s : %7s' % (name.capitalize(), value)) + + +def main(): + print('MEMORY\n------') + pprint_ntuple(psutil.virtual_memory()) + print('\nSWAP\n----') + pprint_ntuple(psutil.swap_memory()) + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/netstat.py b/python/psutil/examples/netstat.py new file mode 100755 index 0000000000..884622e9e3 --- /dev/null +++ b/python/psutil/examples/netstat.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of 'netstat -antp' on Linux. + +$ python examples/netstat.py +Proto Local address Remote address Status PID Program name +tcp 127.0.0.1:48256 127.0.0.1:45884 ESTABLISHED 13646 chrome +tcp 127.0.0.1:47073 127.0.0.1:45884 ESTABLISHED 13646 chrome +tcp 127.0.0.1:47072 127.0.0.1:45884 ESTABLISHED 13646 chrome +tcp 127.0.0.1:45884 - LISTEN 13651 GoogleTalkPlugi +tcp 127.0.0.1:60948 - LISTEN 13651 GoogleTalkPlugi +tcp 172.17.42.1:49102 127.0.0.1:19305 CLOSE_WAIT 13651 GoogleTalkPlugi +tcp 172.17.42.1:55797 127.0.0.1:443 CLOSE_WAIT 13651 GoogleTalkPlugi +... +""" + +import socket +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM + +import psutil + + +AD = "-" +AF_INET6 = getattr(socket, 'AF_INET6', object()) +proto_map = { + (AF_INET, SOCK_STREAM): 'tcp', + (AF_INET6, SOCK_STREAM): 'tcp6', + (AF_INET, SOCK_DGRAM): 'udp', + (AF_INET6, SOCK_DGRAM): 'udp6', +} + + +def main(): + templ = "%-5s %-30s %-30s %-13s %-6s %s" + print(templ % ( + "Proto", "Local address", "Remote address", "Status", "PID", + "Program name")) + proc_names = {} + for p in psutil.process_iter(): + try: + proc_names[p.pid] = p.name() + except psutil.Error: + pass + for c in psutil.net_connections(kind='inet'): + laddr = "%s:%s" % (c.laddr) + raddr = "" + if c.raddr: + raddr = "%s:%s" % (c.raddr) + print(templ % ( + proto_map[(c.family, c.type)], + laddr, + raddr or AD, + c.status, + c.pid or AD, + proc_names.get(c.pid, '?')[:15], + )) + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/nettop.py b/python/psutil/examples/nettop.py new file mode 100755 index 0000000000..7a8343ee4c --- /dev/null +++ b/python/psutil/examples/nettop.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# +# $Id: iotop.py 1160 2011-10-14 18:50:36Z g.rodola@gmail.com $ +# +# 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. + +""" +Shows real-time network statistics. + +Author: Giampaolo Rodola' <g.rodola@gmail.com> + +$ python examples/nettop.py +----------------------------------------------------------- +total bytes: sent: 1.49 G received: 4.82 G +total packets: sent: 7338724 received: 8082712 + +wlan0 TOTAL PER-SEC +----------------------------------------------------------- +bytes-sent 1.29 G 0.00 B/s +bytes-recv 3.48 G 0.00 B/s +pkts-sent 7221782 0 +pkts-recv 6753724 0 + +eth1 TOTAL PER-SEC +----------------------------------------------------------- +bytes-sent 131.77 M 0.00 B/s +bytes-recv 1.28 G 0.00 B/s +pkts-sent 0 0 +pkts-recv 1214470 0 +""" + +import atexit +import time +import sys +try: + import curses +except ImportError: + sys.exit('platform not supported') + +import psutil + + +# --- curses stuff +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + +win = curses.initscr() +atexit.register(tear_down) +curses.endwin() +lineno = 0 + + +def print_line(line, highlight=False): + """A thin wrapper around curses's addstr().""" + global lineno + try: + if highlight: + line += " " * (win.getmaxyx()[1] - len(line)) + win.addstr(lineno, 0, line, curses.A_REVERSE) + else: + win.addstr(lineno, 0, line, 0) + except curses.error: + lineno = 0 + win.refresh() + raise + else: + lineno += 1 +# --- curses stuff + + +def bytes2human(n): + """ + >>> bytes2human(10000) + '9.8 K' + >>> bytes2human(100001221) + '95.4 M' + """ + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.2f %s' % (value, s) + return '%.2f B' % (n) + + +def poll(interval): + """Retrieve raw stats within an interval window.""" + tot_before = psutil.net_io_counters() + pnic_before = psutil.net_io_counters(pernic=True) + # sleep some time + time.sleep(interval) + tot_after = psutil.net_io_counters() + pnic_after = psutil.net_io_counters(pernic=True) + return (tot_before, tot_after, pnic_before, pnic_after) + + +def refresh_window(tot_before, tot_after, pnic_before, pnic_after): + """Print stats on screen.""" + global lineno + + # totals + print_line("total bytes: sent: %-10s received: %s" % ( + bytes2human(tot_after.bytes_sent), + bytes2human(tot_after.bytes_recv)) + ) + print_line("total packets: sent: %-10s received: %s" % ( + tot_after.packets_sent, tot_after.packets_recv)) + + # per-network interface details: let's sort network interfaces so + # that the ones which generated more traffic are shown first + print_line("") + nic_names = list(pnic_after.keys()) + nic_names.sort(key=lambda x: sum(pnic_after[x]), reverse=True) + for name in nic_names: + stats_before = pnic_before[name] + stats_after = pnic_after[name] + templ = "%-15s %15s %15s" + print_line(templ % (name, "TOTAL", "PER-SEC"), highlight=True) + print_line(templ % ( + "bytes-sent", + bytes2human(stats_after.bytes_sent), + bytes2human( + stats_after.bytes_sent - stats_before.bytes_sent) + '/s', + )) + print_line(templ % ( + "bytes-recv", + bytes2human(stats_after.bytes_recv), + bytes2human( + stats_after.bytes_recv - stats_before.bytes_recv) + '/s', + )) + print_line(templ % ( + "pkts-sent", + stats_after.packets_sent, + stats_after.packets_sent - stats_before.packets_sent, + )) + print_line(templ % ( + "pkts-recv", + stats_after.packets_recv, + stats_after.packets_recv - stats_before.packets_recv, + )) + print_line("") + win.refresh() + lineno = 0 + + +def main(): + try: + interval = 0 + while True: + args = poll(interval) + refresh_window(*args) + interval = 1 + except (KeyboardInterrupt, SystemExit): + pass + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/pidof.py b/python/psutil/examples/pidof.py new file mode 100755 index 0000000000..8692a3152b --- /dev/null +++ b/python/psutil/examples/pidof.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Copyright (c) 2009, Giampaolo Rodola', karthikrev. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + + +""" +A clone of 'pidof' cmdline utility. +$ pidof python +1140 1138 1136 1134 1133 1129 1127 1125 1121 1120 1119 +""" + +from __future__ import print_function +import psutil +import sys + + +def pidof(pgname): + pids = [] + for proc in psutil.process_iter(): + # search for matches in the process name and cmdline + try: + name = proc.name() + except psutil.Error: + pass + else: + if name == pgname: + pids.append(str(proc.pid)) + continue + + try: + cmdline = proc.cmdline() + except psutil.Error: + pass + else: + if cmdline and cmdline[0] == pgname: + pids.append(str(proc.pid)) + + return pids + + +def main(): + if len(sys.argv) != 2: + sys.exit('usage: %s pgname' % __file__) + else: + pgname = sys.argv[1] + pids = pidof(pgname) + if pids: + print(" ".join(pids)) + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/pmap.py b/python/psutil/examples/pmap.py new file mode 100755 index 0000000000..7593777aef --- /dev/null +++ b/python/psutil/examples/pmap.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of 'pmap' utility on Linux, 'vmmap' on OSX and 'procstat -v' on BSD. +Report memory map of a process. + +$ python examples/pmap.py 32402 +pid=32402, name=hg +Address RSS Mode Mapping +0000000000400000 1200K r-xp /usr/bin/python2.7 +0000000000838000 4K r--p /usr/bin/python2.7 +0000000000839000 304K rw-p /usr/bin/python2.7 +00000000008ae000 68K rw-p [anon] +000000000275e000 5396K rw-p [heap] +00002b29bb1e0000 124K r-xp /lib/x86_64-linux-gnu/ld-2.17.so +00002b29bb203000 8K rw-p [anon] +00002b29bb220000 528K rw-p [anon] +00002b29bb2d8000 768K rw-p [anon] +00002b29bb402000 4K r--p /lib/x86_64-linux-gnu/ld-2.17.so +00002b29bb403000 8K rw-p /lib/x86_64-linux-gnu/ld-2.17.so +00002b29bb405000 60K r-xp /lib/x86_64-linux-gnu/libpthread-2.17.so +00002b29bb41d000 0K ---p /lib/x86_64-linux-gnu/libpthread-2.17.so +00007fff94be6000 48K rw-p [stack] +00007fff94dd1000 4K r-xp [vdso] +ffffffffff600000 0K r-xp [vsyscall] +... +""" + +import sys + +import psutil + + +def main(): + if len(sys.argv) != 2: + sys.exit('usage: pmap <pid>') + p = psutil.Process(int(sys.argv[1])) + print("pid=%s, name=%s" % (p.pid, p.name())) + templ = "%-16s %10s %-7s %s" + print(templ % ("Address", "RSS", "Mode", "Mapping")) + total_rss = 0 + for m in p.memory_maps(grouped=False): + total_rss += m.rss + print(templ % ( + m.addr.split('-')[0].zfill(16), + str(m.rss / 1024) + 'K', + m.perms, + m.path)) + print("-" * 33) + print(templ % ("Total", str(total_rss / 1024) + 'K', '', '')) + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/process_detail.py b/python/psutil/examples/process_detail.py new file mode 100755 index 0000000000..e20371aefe --- /dev/null +++ b/python/psutil/examples/process_detail.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python + +# 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. + +""" +Print detailed information about a process. +Author: Giampaolo Rodola' <g.rodola@gmail.com> + +$ python examples/process_detail.py +pid 820 +name python +exe /usr/bin/python2.7 +parent 29613 (bash) +cmdline python examples/process_detail.py +started 2014-41-27 03:41 +user giampaolo +uids real=1000, effective=1000, saved=1000 +gids real=1000, effective=1000, saved=1000 +terminal /dev/pts/17 +cwd /ssd/svn/psutil +memory 0.1% (resident=10.6M, virtual=58.5M) +cpu 0.0% (user=0.09, system=0.0) +status running +niceness 0 +num threads 1 +I/O bytes-read=0B, bytes-written=0B +open files +running threads id=820, user-time=0.09, sys-time=0.0 +""" + +import datetime +import os +import socket +import sys + +import psutil + + +POSIX = os.name == 'posix' + + +def convert_bytes(n): + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = float(n) / prefix[s] + return '%.1f%s' % (value, s) + return "%sB" % n + + +def print_(a, b): + if sys.stdout.isatty() and POSIX: + fmt = '\x1b[1;32m%-17s\x1b[0m %s' % (a, b) + else: + fmt = '%-15s %s' % (a, b) + # python 2/3 compatibility layer + sys.stdout.write(fmt + '\n') + sys.stdout.flush() + + +def run(pid): + ACCESS_DENIED = '' + try: + p = psutil.Process(pid) + pinfo = p.as_dict(ad_value=ACCESS_DENIED) + except psutil.NoSuchProcess as err: + sys.exit(str(err)) + + try: + parent = p.parent() + if parent: + parent = '(%s)' % parent.name() + else: + parent = '' + except psutil.Error: + parent = '' + if pinfo['create_time'] != ACCESS_DENIED: + started = datetime.datetime.fromtimestamp( + pinfo['create_time']).strftime('%Y-%m-%d %H:%M') + else: + started = ACCESS_DENIED + io = pinfo.get('io_counters', ACCESS_DENIED) + if pinfo['memory_info'] != ACCESS_DENIED: + mem = '%s%% (resident=%s, virtual=%s) ' % ( + round(pinfo['memory_percent'], 1), + convert_bytes(pinfo['memory_info'].rss), + convert_bytes(pinfo['memory_info'].vms)) + else: + mem = ACCESS_DENIED + children = p.children() + + print_('pid', pinfo['pid']) + print_('name', pinfo['name']) + print_('exe', pinfo['exe']) + print_('parent', '%s %s' % (pinfo['ppid'], parent)) + print_('cmdline', ' '.join(pinfo['cmdline'])) + print_('started', started) + print_('user', pinfo['username']) + if POSIX and pinfo['uids'] and pinfo['gids']: + print_('uids', 'real=%s, effective=%s, saved=%s' % pinfo['uids']) + if POSIX and pinfo['gids']: + print_('gids', 'real=%s, effective=%s, saved=%s' % pinfo['gids']) + if POSIX: + print_('terminal', pinfo['terminal'] or '') + print_('cwd', pinfo['cwd']) + print_('memory', mem) + print_('cpu', '%s%% (user=%s, system=%s)' % ( + pinfo['cpu_percent'], + getattr(pinfo['cpu_times'], 'user', '?'), + getattr(pinfo['cpu_times'], 'system', '?'))) + print_('status', pinfo['status']) + print_('niceness', pinfo['nice']) + print_('num threads', pinfo['num_threads']) + if io != ACCESS_DENIED: + print_('I/O', 'bytes-read=%s, bytes-written=%s' % ( + convert_bytes(io.read_bytes), + convert_bytes(io.write_bytes))) + if children: + print_('children', '') + for child in children: + print_('', 'pid=%s name=%s' % (child.pid, child.name())) + + if pinfo['open_files'] != ACCESS_DENIED: + print_('open files', '') + for file in pinfo['open_files']: + print_('', 'fd=%s %s ' % (file.fd, file.path)) + + if pinfo['threads']: + print_('running threads', '') + for thread in pinfo['threads']: + print_('', 'id=%s, user-time=%s, sys-time=%s' % ( + thread.id, thread.user_time, thread.system_time)) + if pinfo['connections'] not in (ACCESS_DENIED, []): + print_('open connections', '') + for conn in pinfo['connections']: + if conn.type == socket.SOCK_STREAM: + type = 'TCP' + elif conn.type == socket.SOCK_DGRAM: + type = 'UDP' + else: + type = 'UNIX' + lip, lport = conn.laddr + if not conn.raddr: + rip, rport = '*', '*' + else: + rip, rport = conn.raddr + print_('', '%s:%s -> %s:%s type=%s status=%s' % ( + lip, lport, rip, rport, type, conn.status)) + + +def main(argv=None): + if argv is None: + argv = sys.argv + if len(argv) == 1: + sys.exit(run(os.getpid())) + elif len(argv) == 2: + sys.exit(run(int(argv[1]))) + else: + sys.exit('usage: %s [pid]' % __file__) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/python/psutil/examples/ps.py b/python/psutil/examples/ps.py new file mode 100644 index 0000000000..2b67bd18ff --- /dev/null +++ b/python/psutil/examples/ps.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of 'ps -aux' on UNIX. + +$ python examples/ps.py +... +""" + +import datetime +import os +import time + +import psutil + + +def main(): + today_day = datetime.date.today() + templ = "%-10s %5s %4s %4s %7s %7s %-13s %5s %7s %s" + attrs = ['pid', 'cpu_percent', 'memory_percent', 'name', 'cpu_times', + 'create_time', 'memory_info'] + if os.name == 'posix': + attrs.append('uids') + attrs.append('terminal') + print(templ % ("USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", + "START", "TIME", "COMMAND")) + for p in psutil.process_iter(): + try: + pinfo = p.as_dict(attrs, ad_value='') + except psutil.NoSuchProcess: + pass + else: + if pinfo['create_time']: + ctime = datetime.datetime.fromtimestamp(pinfo['create_time']) + if ctime.date() == today_day: + ctime = ctime.strftime("%H:%M") + else: + ctime = ctime.strftime("%b%d") + else: + ctime = '' + cputime = time.strftime("%M:%S", + time.localtime(sum(pinfo['cpu_times']))) + try: + user = p.username() + except KeyError: + if os.name == 'posix': + if pinfo['uids']: + user = str(pinfo['uids'].real) + else: + user = '' + else: + raise + except psutil.Error: + user = '' + if os.name == 'nt' and '\\' in user: + user = user.split('\\')[1] + vms = pinfo['memory_info'] and \ + int(pinfo['memory_info'].vms / 1024) or '?' + rss = pinfo['memory_info'] and \ + int(pinfo['memory_info'].rss / 1024) or '?' + memp = pinfo['memory_percent'] and \ + round(pinfo['memory_percent'], 1) or '?' + print(templ % ( + user[:10], + pinfo['pid'], + pinfo['cpu_percent'], + memp, + vms, + rss, + pinfo.get('terminal', '') or '?', + ctime, + cputime, + pinfo['name'].strip() or '?')) + + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/pstree.py b/python/psutil/examples/pstree.py new file mode 100644 index 0000000000..1bf8c9c049 --- /dev/null +++ b/python/psutil/examples/pstree.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# 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. + +""" +Similar to 'ps aux --forest' on Linux, prints the process list +as a tree structure. + +$ python examples/pstree.py +0 ? +|- 1 init +| |- 289 cgmanager +| |- 616 upstart-socket-bridge +| |- 628 rpcbind +| |- 892 upstart-file-bridge +| |- 907 dbus-daemon +| |- 978 avahi-daemon +| | `_ 979 avahi-daemon +| |- 987 NetworkManager +| | |- 2242 dnsmasq +| | `_ 10699 dhclient +| |- 993 polkitd +| |- 1061 getty +| |- 1066 su +| | `_ 1190 salt-minion... +... +""" + +from __future__ import print_function +import collections +import sys + +import psutil + + +def print_tree(parent, tree, indent=''): + try: + name = psutil.Process(parent).name() + except psutil.Error: + name = "?" + print(parent, name) + if parent not in tree: + return + children = tree[parent][:-1] + for child in children: + sys.stdout.write(indent + "|- ") + print_tree(child, tree, indent + "| ") + child = tree[parent][-1] + sys.stdout.write(indent + "`_ ") + print_tree(child, tree, indent + " ") + + +def main(): + # construct a dict where 'values' are all the processes + # having 'key' as their parent + tree = collections.defaultdict(list) + for p in psutil.process_iter(): + try: + tree[p.ppid()].append(p.pid) + except (psutil.NoSuchProcess, psutil.ZombieProcess): + pass + # on systems supporting PID 0, PID 0's parent is usually 0 + if 0 in tree and 0 in tree[0]: + tree[0].remove(0) + print_tree(min(tree), tree) + + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/top.py b/python/psutil/examples/top.py new file mode 100755 index 0000000000..7aebef1d42 --- /dev/null +++ b/python/psutil/examples/top.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of top / htop. + +Author: Giampaolo Rodola' <g.rodola@gmail.com> + +$ python examples/top.py + CPU0 [| ] 4.9% + CPU1 [||| ] 7.8% + CPU2 [ ] 2.0% + CPU3 [||||| ] 13.9% + Mem [||||||||||||||||||| ] 49.8% 4920M/9888M + Swap [ ] 0.0% 0M/0M + Processes: 287 (running=1 sleeping=286) + Load average: 0.34 0.54 0.46 Uptime: 3 days, 10:16:37 + +PID USER NI VIRT RES CPU% MEM% TIME+ NAME +------------------------------------------------------------ +989 giampaol 0 66M 12M 7.4 0.1 0:00.61 python +2083 root 0 506M 159M 6.5 1.6 0:29.26 Xorg +4503 giampaol 0 599M 25M 6.5 0.3 3:32.60 gnome-terminal +3868 giampaol 0 358M 8M 2.8 0.1 23:12.60 pulseaudio +3936 giampaol 0 1G 111M 2.8 1.1 33:41.67 compiz +4401 giampaol 0 536M 141M 2.8 1.4 35:42.73 skype +4047 giampaol 0 743M 76M 1.8 0.8 42:03.33 unity-panel-service +13155 giampaol 0 1G 280M 1.8 2.8 41:57.34 chrome +10 root 0 0B 0B 0.9 0.0 4:01.81 rcu_sched +339 giampaol 0 1G 113M 0.9 1.1 8:15.73 chrome +... +""" + +from datetime import datetime, timedelta +import atexit +import os +import time +import sys +try: + import curses +except ImportError: + sys.exit('platform not supported') + +import psutil + + +# --- curses stuff +def tear_down(): + win.keypad(0) + curses.nocbreak() + curses.echo() + curses.endwin() + +win = curses.initscr() +atexit.register(tear_down) +curses.endwin() +lineno = 0 + + +def print_line(line, highlight=False): + """A thin wrapper around curses's addstr().""" + global lineno + try: + if highlight: + line += " " * (win.getmaxyx()[1] - len(line)) + win.addstr(lineno, 0, line, curses.A_REVERSE) + else: + win.addstr(lineno, 0, line, 0) + except curses.error: + lineno = 0 + win.refresh() + raise + else: + lineno += 1 +# --- /curses stuff + + +def bytes2human(n): + """ + >>> bytes2human(10000) + '9K' + >>> bytes2human(100001221) + '95M' + """ + symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') + prefix = {} + for i, s in enumerate(symbols): + prefix[s] = 1 << (i + 1) * 10 + for s in reversed(symbols): + if n >= prefix[s]: + value = int(float(n) / prefix[s]) + return '%s%s' % (value, s) + return "%sB" % n + + +def poll(interval): + # sleep some time + time.sleep(interval) + procs = [] + procs_status = {} + for p in psutil.process_iter(): + try: + p.dict = p.as_dict(['username', 'nice', 'memory_info', + 'memory_percent', 'cpu_percent', + 'cpu_times', 'name', 'status']) + try: + procs_status[p.dict['status']] += 1 + except KeyError: + procs_status[p.dict['status']] = 1 + except psutil.NoSuchProcess: + pass + else: + procs.append(p) + + # return processes sorted by CPU percent usage + processes = sorted(procs, key=lambda p: p.dict['cpu_percent'], + reverse=True) + return (processes, procs_status) + + +def print_header(procs_status, num_procs): + """Print system-related info, above the process list.""" + + def get_dashes(perc): + dashes = "|" * int((float(perc) / 10 * 4)) + empty_dashes = " " * (40 - len(dashes)) + return dashes, empty_dashes + + # cpu usage + percs = psutil.cpu_percent(interval=0, percpu=True) + for cpu_num, perc in enumerate(percs): + dashes, empty_dashes = get_dashes(perc) + print_line(" CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, + perc)) + mem = psutil.virtual_memory() + dashes, empty_dashes = get_dashes(mem.percent) + used = mem.total - mem.available + line = " Mem [%s%s] %5s%% %6s/%s" % ( + dashes, empty_dashes, + mem.percent, + str(int(used / 1024 / 1024)) + "M", + str(int(mem.total / 1024 / 1024)) + "M" + ) + print_line(line) + + # swap usage + swap = psutil.swap_memory() + dashes, empty_dashes = get_dashes(swap.percent) + line = " Swap [%s%s] %5s%% %6s/%s" % ( + dashes, empty_dashes, + swap.percent, + str(int(swap.used / 1024 / 1024)) + "M", + str(int(swap.total / 1024 / 1024)) + "M" + ) + print_line(line) + + # processes number and status + st = [] + for x, y in procs_status.items(): + if y: + st.append("%s=%s" % (x, y)) + st.sort(key=lambda x: x[:3] in ('run', 'sle'), reverse=1) + print_line(" Processes: %s (%s)" % (num_procs, ' '.join(st))) + # load average, uptime + uptime = datetime.now() - datetime.fromtimestamp(psutil.boot_time()) + av1, av2, av3 = os.getloadavg() + line = " Load average: %.2f %.2f %.2f Uptime: %s" \ + % (av1, av2, av3, str(uptime).split('.')[0]) + print_line(line) + + +def refresh_window(procs, procs_status): + """Print results on screen by using curses.""" + curses.endwin() + templ = "%-6s %-8s %4s %5s %5s %6s %4s %9s %2s" + win.erase() + header = templ % ("PID", "USER", "NI", "VIRT", "RES", "CPU%", "MEM%", + "TIME+", "NAME") + print_header(procs_status, len(procs)) + print_line("") + print_line(header, highlight=True) + for p in procs: + # TIME+ column shows process CPU cumulative time and it + # is expressed as: "mm:ss.ms" + if p.dict['cpu_times'] is not None: + ctime = timedelta(seconds=sum(p.dict['cpu_times'])) + ctime = "%s:%s.%s" % (ctime.seconds // 60 % 60, + str((ctime.seconds % 60)).zfill(2), + str(ctime.microseconds)[:2]) + else: + ctime = '' + if p.dict['memory_percent'] is not None: + p.dict['memory_percent'] = round(p.dict['memory_percent'], 1) + else: + p.dict['memory_percent'] = '' + if p.dict['cpu_percent'] is None: + p.dict['cpu_percent'] = '' + if p.dict['username']: + username = p.dict['username'][:8] + else: + username = "" + line = templ % (p.pid, + username, + p.dict['nice'], + bytes2human(getattr(p.dict['memory_info'], 'vms', 0)), + bytes2human(getattr(p.dict['memory_info'], 'rss', 0)), + p.dict['cpu_percent'], + p.dict['memory_percent'], + ctime, + p.dict['name'] or '', + ) + try: + print_line(line) + except curses.error: + break + win.refresh() + + +def main(): + try: + interval = 0 + while True: + args = poll(interval) + refresh_window(*args) + interval = 1 + except (KeyboardInterrupt, SystemExit): + pass + +if __name__ == '__main__': + main() diff --git a/python/psutil/examples/who.py b/python/psutil/examples/who.py new file mode 100755 index 0000000000..b382bebfa3 --- /dev/null +++ b/python/psutil/examples/who.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# 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. + +""" +A clone of 'who' command; print information about users who are +currently logged in. + +$ python examples/who.py +giampaolo tty7 2014-02-23 17:25 (:0) +giampaolo pts/7 2014-02-24 18:25 (:192.168.1.56) +giampaolo pts/8 2014-02-24 18:25 (:0) +giampaolo pts/9 2014-02-27 01:32 (:0) +""" + +from datetime import datetime + +import psutil + + +def main(): + users = psutil.users() + for user in users: + print("%-15s %-15s %s (%s)" % ( + user.name, + user.terminal or '-', + datetime.fromtimestamp(user.started).strftime("%Y-%m-%d %H:%M"), + user.host)) + +if __name__ == '__main__': + main() diff --git a/python/psutil/make.bat b/python/psutil/make.bat new file mode 100644 index 0000000000..9c430101dd --- /dev/null +++ b/python/psutil/make.bat @@ -0,0 +1,201 @@ +@echo off + +rem ========================================================================== +rem Shortcuts for various tasks, emulating UNIX "make" on Windows. +rem It is primarly intended as a shortcut for compiling / installing +rem psutil ("make.bat build", "make.bat install") and running tests +rem ("make.bat test"). +rem +rem This script is modeled after my Windows installation which uses: +rem - Visual studio 2008 for Python 2.6, 2.7, 3.2 +rem - Visual studio 2010 for Python 3.3+ +rem ...therefore it might not work on your Windows installation. +rem +rem By default C:\Python27\python.exe is used. +rem To compile for a specific Python version run: +rem set PYTHON=C:\Python34\python.exe & make.bat build +rem +rem To use a different test script: +rem set PYTHON=C:\Python34\python.exe & set TSCRIPT=foo.py & make.bat test +rem ========================================================================== + +if "%PYTHON%" == "" ( + set PYTHON=C:\Python27\python.exe +) +if "%TSCRIPT%" == "" ( + set TSCRIPT=test\test_psutil.py +) + +set PYTHON26=C:\Python26\python.exe +set PYTHON27=C:\Python27\python.exe +set PYTHON33=C:\Python33\python.exe +set PYTHON34=C:\Python34\python.exe +set PYTHON26-64=C:\Python26-64\python.exe +set PYTHON27-64=C:\Python27-64\python.exe +set PYTHON33-64=C:\Python33-64\python.exe +set PYTHON34-64=C:\Python34-64\python.exe + +set ALL_PYTHONS=%PYTHON26% %PYTHON27% %PYTHON33% %PYTHON34% %PYTHON26-64% %PYTHON27-64% %PYTHON33-64% %PYTHON34-64% + +rem Needed to locate the .pypirc file and upload exes on PYPI. +set HOME=%USERPROFILE% + +rem ========================================================================== + +if "%1" == "help" ( + :help + echo Run `make ^<target^>` where ^<target^> is one of: + echo build compile without installing + echo build-all build exes + wheels + echo clean clean build files + echo flake8 run flake8 + echo install compile and install + echo setup-dev-env install pip, pywin32, wheels, etc. for all python versions + echo test run tests + echo test-memleaks run memory leak tests + echo test-process run process related tests + echo test-system run system APIs related tests + echo uninstall uninstall + echo upload-all upload exes + wheels + goto :eof +) + +if "%1" == "clean" ( + for /r %%R in (__pycache__) do if exist %%R (rmdir /S /Q %%R) + for /r %%R in (*.pyc) do if exist %%R (del /s %%R) + for /r %%R in (*.pyd) do if exist %%R (del /s %%R) + for /r %%R in (*.orig) do if exist %%R (del /s %%R) + for /r %%R in (*.bak) do if exist %%R (del /s %%R) + for /r %%R in (*.rej) do if exist %%R (del /s %%R) + if exist psutil.egg-info (rmdir /S /Q psutil.egg-info) + if exist build (rmdir /S /Q build) + if exist dist (rmdir /S /Q dist) + goto :eof +) + +if "%1" == "build" ( + :build + "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat" + %PYTHON% setup.py build + if %errorlevel% neq 0 goto :error + rem copies *.pyd files in ./psutil directory in order to allow + rem "import psutil" when using the interactive interpreter from + rem within this directory. + %PYTHON% setup.py build_ext -i + if %errorlevel% neq 0 goto :error + goto :eof +) + +if "%1" == "install" ( + :install + call :build + %PYTHON% setup.py install + goto :eof +) + +if "%1" == "uninstall" ( + for %%A in ("%PYTHON%") do ( + set folder=%%~dpA + ) + for /F "delims=" %%i in ('dir /b %folder%\Lib\site-packages\*psutil*') do ( + rmdir /S /Q %folder%\Lib\site-packages\%%i + ) + goto :eof +) + +if "%1" == "test" ( + call :install + %PYTHON% %TSCRIPT% + goto :eof +) + +if "%1" == "test-process" ( + call :install + %PYTHON% -m unittest -v test.test_psutil.TestProcess + goto :eof +) + +if "%1" == "test-system" ( + call :install + %PYTHON% -m unittest -v test.test_psutil.TestSystem + goto :eof +) + +if "%1" == "test-memleaks" ( + call :install + %PYTHON% test\test_memory_leaks.py + goto :eof +) + +if "%1" == "build-all" ( + :build-all + "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat" + for %%P in (%ALL_PYTHONS%) do ( + @echo ------------------------------------------------ + @echo building exe for %%P + @echo ------------------------------------------------ + %%P setup.py build bdist_wininst || goto :error + @echo ------------------------------------------------ + @echo building wheel for %%P + @echo ------------------------------------------------ + %%P setup.py build bdist_wheel || goto :error + ) + echo OK + goto :eof +) + +if "%1" == "upload-all" ( + :upload-exes + "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin\vcvars64.bat" + for %%P in (%ALL_PYTHONS%) do ( + @echo ------------------------------------------------ + @echo uploading exe for %%P + @echo ------------------------------------------------ + %%P setup.py build bdist_wininst upload || goto :error + @echo ------------------------------------------------ + @echo uploading wheel for %%P + @echo ------------------------------------------------ + %%P setup.py build bdist_wheel upload || goto :error + ) + echo OK + goto :eof +) + +if "%1" == "setup-dev-env" ( + :setup-env + @echo ------------------------------------------------ + @echo downloading pip installer + @echo ------------------------------------------------ + C:\python27\python.exe -c "import urllib2; r = urllib2.urlopen('https://raw.github.com/pypa/pip/master/contrib/get-pip.py'); open('get-pip.py', 'wb').write(r.read())" + for %%P in (%ALL_PYTHONS%) do ( + @echo ------------------------------------------------ + @echo installing pip for %%P + @echo ------------------------------------------------ + %%P get-pip.py + ) + for %%P in (%ALL_PYTHONS%) do ( + @echo ------------------------------------------------ + @echo installing deps for %%P + @echo ------------------------------------------------ + rem mandatory / for unittests + %%P -m pip install unittest2 ipaddress mock wmi wheel pypiwin32 --upgrade + rem nice to have + %%P -m pip install ipdb pep8 pyflakes flake8 --upgrade + ) + goto :eof +) + +if "%1" == "flake8" ( + :flake8 + %PYTHON% -c "from flake8.main import main; main()" + goto :eof +) + +goto :help + +:error + @echo ------------------------------------------------ + @echo last command exited with error code %errorlevel% + @echo ------------------------------------------------ + @exit /b %errorlevel% + goto :eof diff --git a/python/psutil/psutil/__init__.py b/python/psutil/psutil/__init__.py new file mode 100644 index 0000000000..1444425b8d --- /dev/null +++ b/python/psutil/psutil/__init__.py @@ -0,0 +1,1887 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# 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. + +"""psutil is a cross-platform library for retrieving information on +running processes and system utilization (CPU, memory, disks, network) +in Python. +""" + +from __future__ import division + +import collections +import errno +import functools +import os +import signal +import subprocess +import sys +import time +try: + import pwd +except ImportError: + pwd = None + +from . import _common +from ._common import memoize +from ._compat import callable, long +from ._compat import PY3 as _PY3 + +from ._common import (STATUS_RUNNING, # NOQA + STATUS_SLEEPING, + STATUS_DISK_SLEEP, + STATUS_STOPPED, + STATUS_TRACING_STOP, + STATUS_ZOMBIE, + STATUS_DEAD, + STATUS_WAKING, + STATUS_LOCKED, + STATUS_IDLE, # bsd + STATUS_WAITING) # bsd + +from ._common import (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_NONE) + +from ._common import (NIC_DUPLEX_FULL, # NOQA + NIC_DUPLEX_HALF, + NIC_DUPLEX_UNKNOWN) + +if sys.platform.startswith("linux"): + from . import _pslinux as _psplatform + + from ._pslinux import (IOPRIO_CLASS_NONE, # NOQA + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE) + # Linux >= 2.6.36 + if _psplatform.HAS_PRLIMIT: + from ._psutil_linux import (RLIM_INFINITY, # NOQA + RLIMIT_AS, + RLIMIT_CORE, + RLIMIT_CPU, + RLIMIT_DATA, + RLIMIT_FSIZE, + RLIMIT_LOCKS, + RLIMIT_MEMLOCK, + RLIMIT_NOFILE, + RLIMIT_NPROC, + RLIMIT_RSS, + RLIMIT_STACK) + # Kinda ugly but considerably faster than using hasattr() and + # setattr() against the module object (we are at import time: + # speed matters). + from . import _psutil_linux + try: + RLIMIT_MSGQUEUE = _psutil_linux.RLIMIT_MSGQUEUE + except AttributeError: + pass + try: + RLIMIT_NICE = _psutil_linux.RLIMIT_NICE + except AttributeError: + pass + try: + RLIMIT_RTPRIO = _psutil_linux.RLIMIT_RTPRIO + except AttributeError: + pass + try: + RLIMIT_RTTIME = _psutil_linux.RLIMIT_RTTIME + except AttributeError: + pass + try: + RLIMIT_SIGPENDING = _psutil_linux.RLIMIT_SIGPENDING + except AttributeError: + pass + del _psutil_linux + +elif sys.platform.startswith("win32"): + from . import _pswindows as _psplatform + from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, # NOQA + BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, + IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, + REALTIME_PRIORITY_CLASS) + from ._pswindows import CONN_DELETE_TCB # NOQA + +elif sys.platform.startswith("darwin"): + from . import _psosx as _psplatform + +elif sys.platform.startswith("freebsd"): + from . import _psbsd as _psplatform + +elif sys.platform.startswith("sunos"): + from . import _pssunos as _psplatform + from ._pssunos import (CONN_IDLE, # NOQA + CONN_BOUND) + +else: # pragma: no cover + raise NotImplementedError('platform %s is not supported' % sys.platform) + + +__all__ = [ + # exceptions + "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied", + "TimeoutExpired", + # constants + "version_info", "__version__", + "STATUS_RUNNING", "STATUS_IDLE", "STATUS_SLEEPING", "STATUS_DISK_SLEEP", + "STATUS_STOPPED", "STATUS_TRACING_STOP", "STATUS_ZOMBIE", "STATUS_DEAD", + "STATUS_WAKING", "STATUS_LOCKED", "STATUS_WAITING", "STATUS_LOCKED", + "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_NONE", + "AF_LINK", + "NIC_DUPLEX_FULL", "NIC_DUPLEX_HALF", "NIC_DUPLEX_UNKNOWN", + # classes + "Process", "Popen", + # functions + "pid_exists", "pids", "process_iter", "wait_procs", # proc + "virtual_memory", "swap_memory", # memory + "cpu_times", "cpu_percent", "cpu_times_percent", "cpu_count", # cpu + "net_io_counters", "net_connections", "net_if_addrs", # network + "net_if_stats", + "disk_io_counters", "disk_partitions", "disk_usage", # disk + "users", "boot_time", # others +] +__all__.extend(_psplatform.__extra__all__) +__author__ = "Giampaolo Rodola'" +__version__ = "3.1.1" +version_info = tuple([int(num) for num in __version__.split('.')]) +AF_LINK = _psplatform.AF_LINK +_TOTAL_PHYMEM = None +_POSIX = os.name == 'posix' +_WINDOWS = os.name == 'nt' +_timer = getattr(time, 'monotonic', time.time) + + +# Sanity check in case the user messed up with psutil installation +# or did something weird with sys.path. In this case we might end +# up importing a python module using a C extension module which +# 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 (different than %s)" % (_psplatform.cext.__file__, + __version__) + raise ImportError(msg) + + +# ===================================================================== +# --- exceptions +# ===================================================================== + +class Error(Exception): + """Base exception class. All other psutil exceptions inherit + from this one. + """ + + def __init__(self, msg=""): + self.msg = msg + + def __repr__(self): + ret = "%s.%s %s" % (self.__class__.__module__, + self.__class__.__name__, self.msg) + return ret.strip() + + __str__ = __repr__ + + +class NoSuchProcess(Error): + """Exception raised when a process with a certain PID doesn't + or no longer exists. + """ + + def __init__(self, pid, name=None, msg=None): + Error.__init__(self, msg) + 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 + + +class ZombieProcess(NoSuchProcess): + """Exception raised when querying a zombie process. This is + raised on OSX, BSD and Solaris only, and not always: depending + on the query the OS may be able to succeed anyway. + On Linux all zombie processes are querable (hence this is never + raised). Windows doesn't have zombie processes. + """ + + def __init__(self, pid, name=None, ppid=None, msg=None): + Error.__init__(self, msg) + self.pid = pid + self.ppid = ppid + self.name = name + self.msg = msg + if msg is None: + if name and ppid: + details = "(pid=%s, name=%s, ppid=%s)" % ( + self.pid, repr(self.name), self.ppid) + elif name: + details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) + else: + details = "(pid=%s)" % self.pid + self.msg = "process still exists but it's a zombie " + details + + +class AccessDenied(Error): + """Exception raised when permission to perform an action is denied.""" + + def __init__(self, pid=None, name=None, msg=None): + Error.__init__(self, msg) + 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 = "" + + +class TimeoutExpired(Error): + """Raised on Process.wait(timeout) if timeout expires and process + is still alive. + """ + + def __init__(self, seconds, pid=None, name=None): + Error.__init__(self, "timeout after %s seconds" % seconds) + 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 + + +# push exception classes into platform specific module namespace +_psplatform.NoSuchProcess = NoSuchProcess +_psplatform.ZombieProcess = ZombieProcess +_psplatform.AccessDenied = AccessDenied +_psplatform.TimeoutExpired = TimeoutExpired + + +# ===================================================================== +# --- Process class +# ===================================================================== + + +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 + + +class Process(object): + """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. + + Note that most of the methods of this class do not make sure + the PID of the process being queried has been reused over time. + That means you might end up retrieving an information referring + to another process in case the original one this instance + refers to is gone in the meantime. + + The only exceptions for which process identity is pre-emptively + checked and guaranteed are: + + - parent() + - children() + - nice() (set) + - ionice() (set) + - rlimit() (set) + - cpu_affinity (set) + - suspend() + - resume() + - send_signal() + - terminate() + - kill() + + To prevent this problem for all other methods you can: + - 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 + """ + + def __init__(self, pid=None): + self._init(pid) + + def _init(self, pid, _ignore_nsp=False): + if pid is None: + pid = os.getpid() + else: + if not _PY3 and not isinstance(pid, (int, long)): + raise TypeError('pid must be an integer (got %r)' % pid) + if pid < 0: + raise ValueError('pid must be a positive integer (got %s)' + % pid) + self._pid = pid + self._name = None + self._exe = None + self._create_time = None + self._gone = False + self._hash = None + # used for caching on Windows only (on POSIX ppid may change) + self._ppid = None + # platform-specific modules define an _psplatform.Process + # implementation class + self._proc = _psplatform.Process(pid) + self._last_sys_cpu_times = None + self._last_proc_cpu_times = None + # cache creation time for later use in is_running() method + try: + self.create_time() + except AccessDenied: + # we should never get here as AFAIK we're able to get + # process creation time on all platforms even as a + # limited user + pass + except ZombieProcess: + # Let's consider a zombie process as legitimate as + # tehcnically it's still alive (it can be queried, + # although not always, and it's returned by pids()). + pass + except NoSuchProcess: + if not _ignore_nsp: + msg = 'no process found with pid %s' % pid + raise NoSuchProcess(pid, None, msg) + else: + self._gone = True + # This pair is supposed to indentify 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: + pid = self.pid + name = repr(self.name()) + except ZombieProcess: + details = "(pid=%s (zombie))" % self.pid + except NoSuchProcess: + details = "(pid=%s (terminated))" % self.pid + except AccessDenied: + details = "(pid=%s)" % (self.pid) + else: + details = "(pid=%s, name=%s)" % (pid, name) + return "%s.%s%s" % (self.__class__.__module__, + self.__class__.__name__, details) + + def __repr__(self): + return "<%s at %s>" % (self.__str__(), id(self)) + + def __eq__(self, other): + # Test for equality with another Process object based + # on PID and creation time. + if not isinstance(other, Process): + return NotImplemented + return self._ident == other._ident + + def __ne__(self, other): + return not self == other + + def __hash__(self): + if self._hash is None: + self._hash = hash(self._ident) + return self._hash + + # --- utility methods + + def as_dict(self, attrs=None, ad_value=None): + """Utility method returning process information as a + hashable dictionary. + + If 'attrs' is specified it must be a list of strings + reflecting available Process class' attribute names + (e.g. ['cpu_times', 'name']) else all public (read + only) attributes are assumed. + + 'ad_value' is the value which gets assigned in case + AccessDenied or ZombieProcess exception is raised when + retrieving that particular process information. + """ + excluded_names = set( + ['send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + 'is_running', 'as_dict', 'parent', 'children', 'rlimit']) + retdict = dict() + ls = set(attrs or [x for x in dir(self)]) + for name in ls: + if name.startswith('_'): + continue + if name in excluded_names: + continue + try: + attr = getattr(self, name) + if callable(attr): + ret = attr() + else: + ret = attr + except (AccessDenied, ZombieProcess): + ret = ad_value + except NotImplementedError: + # in case of not implemented functionality (may happen + # on old or exotic systems) we want to crash only if + # the user explicitly asked for that particular attr + if attrs: + raise + continue + retdict[name] = ret + return retdict + + def parent(self): + """Return the parent process as a Process object pre-emptively + checking whether PID has been reused. + If no parent is known return None. + """ + ppid = self.ppid() + if ppid is not None: + ctime = self.create_time() + try: + parent = Process(ppid) + if parent.create_time() <= ctime: + return parent + # ...else ppid has been reused by another process + except NoSuchProcess: + pass + + def is_running(self): + """Return whether this process is running. + It also checks if PID has been reused by another process in + which case return False. + """ + if self._gone: + return False + try: + # Checking if PID is alive is not enough as the PID might + # have been reused by another process: we also want to + # check process identity. + # Process identity / uniqueness over time is greanted by + # (PID + creation time) and that is verified in __eq__. + return self == Process(self.pid) + except NoSuchProcess: + self._gone = True + return False + + # --- actual API + + @property + def pid(self): + """The process PID.""" + return self._pid + + def ppid(self): + """The process parent PID. + On Windows the return value is cached after first call. + """ + # On POSIX we don't want to cache the ppid as it may unexpectedly + # change to 1 (init) in case this process turns into a zombie: + # https://github.com/giampaolo/psutil/issues/321 + # http://stackoverflow.com/questions/356722/ + + # XXX should we check creation time here rather than in + # Process.parent()? + if _POSIX: + return self._proc.ppid() + else: + self._ppid = self._ppid or self._proc.ppid() + return self._ppid + + def name(self): + """The process name. The return value is cached after first call.""" + if self._name is None: + name = self._proc.name() + if _POSIX and len(name) >= 15: + # On UNIX the name gets truncated to the first 15 characters. + # If it matches the first part of the cmdline we return that + # one instead because it's usually more explicative. + # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". + try: + cmdline = self.cmdline() + except AccessDenied: + pass + else: + if cmdline: + extended_name = os.path.basename(cmdline[0]) + if extended_name.startswith(name): + name = extended_name + self._proc._name = name + self._name = name + return self._name + + def exe(self): + """The process executable as an absolute path. + 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 + cmdline = self.cmdline() + if cmdline and hasattr(os, 'access') and hasattr(os, 'X_OK'): + exe = cmdline[0] # the possible exe + # 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)): + return exe + if isinstance(fallback, AccessDenied): + raise fallback + return fallback + + if self._exe is None: + try: + exe = self._proc.exe() + except AccessDenied as err: + return guess_it(fallback=err) + else: + if not exe: + # underlying implementation can legitimately return an + # empty string; if that's the case we don't want to + # raise AD while guessing from the cmdline + try: + exe = guess_it(fallback=exe) + except AccessDenied: + pass + self._exe = exe + return self._exe + + def cmdline(self): + """The command line this process has been called with.""" + return self._proc.cmdline() + + def status(self): + """The process current status as a STATUS_* constant.""" + try: + return self._proc.status() + except ZombieProcess: + return STATUS_ZOMBIE + + def username(self): + """The name of the user that owns the process. + On UNIX this is calculated by using *real* process uid. + """ + if _POSIX: + if pwd is None: + # might happen if python was installed from sources + raise ImportError( + "requires pwd module shipped with standard python") + real_uid = self.uids().real + try: + return pwd.getpwuid(real_uid).pw_name + except KeyError: + # the uid can't be resolved by the system + return str(real_uid) + else: + return self._proc.username() + + def create_time(self): + """The process creation time as a floating point number + expressed in seconds since the epoch, in UTC. + The return value is cached after first call. + """ + if self._create_time is None: + self._create_time = self._proc.create_time() + return self._create_time + + def cwd(self): + """Process current working directory as an absolute path.""" + return self._proc.cwd() + + def nice(self, value=None): + """Get or set process niceness (priority).""" + if value is None: + return self._proc.nice_get() + else: + if not self.is_running(): + raise NoSuchProcess(self.pid, self._name) + self._proc.nice_set(value) + + if _POSIX: + + def uids(self): + """Return process UIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.uids() + + def gids(self): + """Return process GIDs as a (real, effective, saved) + namedtuple. + """ + return self._proc.gids() + + def terminal(self): + """The terminal associated with this process, if any, + else None. + """ + return self._proc.terminal() + + def num_fds(self): + """Return the number of file descriptors opened by this + process (POSIX only). + """ + return self._proc.num_fds() + + # Linux, BSD and Windows only + if hasattr(_psplatform.Process, "io_counters"): + + def io_counters(self): + """Return process I/O statistics as a + (read_count, write_count, read_bytes, write_bytes) + namedtuple. + Those are the number of read/write calls performed and the + amount of bytes read and written by the process. + """ + return self._proc.io_counters() + + # Linux and Windows >= Vista only + if hasattr(_psplatform.Process, "ionice_get"): + + def ionice(self, ioclass=None, value=None): + """Get or set process I/O niceness (priority). + + On Linux 'ioclass' is one of the IOPRIO_CLASS_* constants. + 'value' is a number which goes from 0 to 7. The higher the + value, the lower the I/O priority of the process. + + On Windows only 'ioclass' is used and it can be set to 2 + (normal), 1 (low) or 0 (very low). + + Available on Linux and Windows > Vista only. + """ + if ioclass is None: + if value is not None: + raise ValueError("'ioclass' argument must be specified") + return self._proc.ionice_get() + else: + return self._proc.ionice_set(ioclass, value) + + # Linux only + if hasattr(_psplatform.Process, "rlimit"): + + def rlimit(self, resource, limits=None): + """Get or set process resource limits as a (soft, hard) + tuple. + + 'resource' is one of the RLIMIT_* constants. + 'limits' is supposed to be a (soft, hard) tuple. + + See "man prlimit" for further info. + Available on Linux only. + """ + if limits is None: + return self._proc.rlimit(resource) + else: + return self._proc.rlimit(resource, limits) + + # Windows, Linux and BSD only + if hasattr(_psplatform.Process, "cpu_affinity_get"): + + def cpu_affinity(self, cpus=None): + """Get or set process CPU affinity. + If specified 'cpus' must be a list of CPUs for which you + want to set the affinity (e.g. [0, 1]). + (Windows, Linux and BSD only). + """ + # Automatically remove duplicates both on get and + # set (for get it's not really necessary, it's + # just for extra safety). + if cpus is None: + return list(set(self._proc.cpu_affinity_get())) + else: + self._proc.cpu_affinity_set(list(set(cpus))) + + if _WINDOWS: + + def num_handles(self): + """Return the number of handles opened by this process + (Windows only). + """ + return self._proc.num_handles() + + def num_ctx_switches(self): + """Return the number of voluntary and involuntary context + switches performed by this process. + """ + return self._proc.num_ctx_switches() + + def num_threads(self): + """Return the number of threads used by this process.""" + return self._proc.num_threads() + + def threads(self): + """Return threads opened by process as a list of + (id, user_time, system_time) namedtuples representing + thread id and thread CPU times (user/system). + """ + 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. + If recursive is True return all the parent descendants. + + Example (A == this process): + + A ─┐ + │ + ├─ B (child) ─┐ + │ └─ X (grandchild) ─┐ + │ └─ Y (great grandchild) + ├─ C (child) + └─ D (child) + + >>> import psutil + >>> p = psutil.Process() + >>> p.children() + B, C, D + >>> p.children(recursive=True) + B, X, Y, C, D + + Note that in the example above if process X disappears + process Y won't be listed as the reference to process A + is lost. + """ + if hasattr(_psplatform, 'ppid_map'): + # Windows only: obtain a {pid:ppid, ...} dict for all running + # processes in one shot (faster). + ppid_map = _psplatform.ppid_map() + else: + ppid_map = None + + ret = [] + if not recursive: + if ppid_map is None: + # 'slow' version, common to all platforms except Windows + for p in process_iter(): + try: + if p.ppid() == self.pid: + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= p.create_time(): + ret.append(p) + except (NoSuchProcess, ZombieProcess): + pass + else: + # Windows only (faster) + for pid, ppid in ppid_map.items(): + if ppid == self.pid: + try: + child = Process(pid) + # if child happens to be older than its parent + # (self) it means child's PID has been reused + if self.create_time() <= child.create_time(): + ret.append(child) + except (NoSuchProcess, ZombieProcess): + pass + else: + # construct a dict where 'values' are all the processes + # having 'key' as their parent + table = collections.defaultdict(list) + if ppid_map is None: + for p in process_iter(): + try: + table[p.ppid()].append(p) + except (NoSuchProcess, ZombieProcess): + pass + else: + for pid, ppid in ppid_map.items(): + try: + p = Process(pid) + table[ppid].append(p) + except (NoSuchProcess, ZombieProcess): + pass + # At this point we have a mapping table where table[self.pid] + # are the current process' children. + # Below, we look for all descendants recursively, similarly + # to a recursive function call. + checkpids = [self.pid] + for pid in checkpids: + for child in table[pid]: + try: + # if child happens to be older than its parent + # (self) it means child's PID has been reused + intime = self.create_time() <= child.create_time() + except (NoSuchProcess, ZombieProcess): + pass + else: + if intime: + ret.append(child) + if child.pid not in checkpids: + checkpids.append(child.pid) + return ret + + def cpu_percent(self, interval=None): + """Return a float representing the current process CPU + utilization as a percentage. + + When interval is 0.0 or None (default) compares process times + to system CPU times elapsed since last call, returning + immediately (non-blocking). That means that the first time + this is called it will return a meaningful 0.0 value. + + When interval is > 0.0 compares process times to system CPU + times elapsed before and after the interval (blocking). + + In this case is recommended for accuracy that this function + be called with at least 0.1 seconds between calls. + + Examples: + + >>> import psutil + >>> p = psutil.Process(os.getpid()) + >>> # blocking + >>> p.cpu_percent(interval=1) + 2.0 + >>> # non-blocking (percentage since last call) + >>> p.cpu_percent(interval=None) + 2.9 + >>> + """ + blocking = interval is not None and interval > 0.0 + num_cpus = cpu_count() + if _POSIX: + def timer(): + return _timer() * num_cpus + else: + def timer(): + return sum(cpu_times()) + if blocking: + st1 = timer() + pt1 = self._proc.cpu_times() + time.sleep(interval) + st2 = timer() + pt2 = self._proc.cpu_times() + else: + st1 = self._last_sys_cpu_times + pt1 = self._last_proc_cpu_times + st2 = timer() + pt2 = self._proc.cpu_times() + if st1 is None or pt1 is None: + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + return 0.0 + + delta_proc = (pt2.user - pt1.user) + (pt2.system - pt1.system) + delta_time = st2 - st1 + # reset values for next call in case of interval == None + self._last_sys_cpu_times = st2 + self._last_proc_cpu_times = pt2 + + try: + # The utilization split between all CPUs. + # Note: a percentage > 100 is legitimate as it can result + # from a process with multiple threads running on different + # CPU cores, see: + # http://stackoverflow.com/questions/1032357 + # https://github.com/giampaolo/psutil/issues/474 + overall_percent = ((delta_proc / delta_time) * 100) * num_cpus + except ZeroDivisionError: + # interval was too low + return 0.0 + else: + return round(overall_percent, 1) + + def cpu_times(self): + """Return a (user, system) namedtuple representing the + accumulated process time, in seconds. + This is the same as os.times() but per-process. + """ + return self._proc.cpu_times() + + def memory_info(self): + """Return a tuple representing RSS (Resident Set Size) and VMS + (Virtual Memory Size) in bytes. + + On UNIX RSS and VMS are the same values shown by 'ps'. + + On Windows RSS and VMS refer to "Mem Usage" and "VM Size" + columns of taskmgr.exe. + """ + return self._proc.memory_info() + + def memory_info_ex(self): + """Return a namedtuple with variable fields depending on the + platform representing extended memory information about + this process. All numbers are expressed in bytes. + """ + return self._proc.memory_info_ex() + + def memory_percent(self): + """Compare physical system memory to process resident memory + (RSS) and calculate process memory utilization as a percentage. + """ + rss = self._proc.memory_info()[0] + # use cached value if available + total_phymem = _TOTAL_PHYMEM or virtual_memory().total + try: + return (rss / float(total_phymem)) * 100 + except ZeroDivisionError: + return 0.0 + + def memory_maps(self, grouped=True): + """Return process' mapped memory regions as a list of namedtuples + whose fields are variable depending on the platform. + + If 'grouped' is True the mapped regions with the same 'path' + are grouped together and the different memory fields are summed. + + If 'grouped' is False every mapped region is shown as a single + entity and the namedtuple will also include the mapped region's + address space ('addr') and permission set ('perms'). + """ + it = self._proc.memory_maps() + if grouped: + d = {} + for tupl in it: + path = tupl[2] + nums = tupl[3:] + try: + d[path] = map(lambda x, y: x + y, d[path], nums) + except KeyError: + d[path] = nums + nt = _psplatform.pmmap_grouped + return [nt(path, *d[path]) for path in d] # NOQA + else: + nt = _psplatform.pmmap_ext + return [nt(*x) for x in it] + + def open_files(self): + """Return files opened by process as a list of + (path, fd) namedtuples including the absolute file name + and file descriptor number. + """ + return self._proc.open_files() + + def connections(self, kind='inet'): + """Return connections opened by process as a list of + (fd, family, type, laddr, raddr, status) namedtuples. + The 'kind' parameter filters for connections that match the + following criteria: + + Kind Value Connections using + inet IPv4 and IPv6 + inet4 IPv4 + inet6 IPv6 + tcp TCP + tcp4 TCP over IPv4 + tcp6 TCP over IPv6 + udp UDP + udp4 UDP over IPv4 + udp6 UDP over IPv6 + unix UNIX socket (both UDP and TCP protocols) + all the sum of all the possible families and protocols + """ + return self._proc.connections(kind) + + if _POSIX: + def _send_signal(self, sig): + if self.pid == 0: + # see "man 2 kill" + raise ValueError( + "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") + try: + os.kill(self.pid, sig) + except OSError as err: + if err.errno == errno.ESRCH: + self._gone = True + raise NoSuchProcess(self.pid, self._name) + if err.errno == errno.EPERM: + raise AccessDenied(self.pid, self._name) + raise + + @_assert_pid_not_reused + def send_signal(self, sig): + """Send a signal to process pre-emptively checking whether + PID has been reused (see signal module constants) . + On Windows only SIGTERM is valid and is treated as an alias + for kill(). + """ + if _POSIX: + self._send_signal(sig) + else: + if sig == signal.SIGTERM: + self._proc.kill() + else: + raise ValueError("only SIGTERM is supported on Windows") + + @_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. + """ + if _POSIX: + self._send_signal(signal.SIGSTOP) + else: + self._proc.suspend() + + @_assert_pid_not_reused + def resume(self): + """Resume process execution with SIGCONT pre-emptively checking + whether PID has been reused. + On Windows this has the effect of resuming all process threads. + """ + if _POSIX: + self._send_signal(signal.SIGCONT) + else: + self._proc.resume() + + @_assert_pid_not_reused + def terminate(self): + """Terminate the process with SIGTERM pre-emptively checking + whether PID has been reused. + On Windows this is an alias for kill(). + """ + if _POSIX: + self._send_signal(signal.SIGTERM) + else: + self._proc.kill() + + @_assert_pid_not_reused + def kill(self): + """Kill the current process with SIGKILL pre-emptively checking + whether PID has been reused. + """ + if _POSIX: + self._send_signal(signal.SIGKILL) + else: + self._proc.kill() + + def wait(self, timeout=None): + """Wait for process to terminate and, if process is a children + of os.getpid(), also return its exit code, else None. + + If the process is already terminated immediately return None + instead of raising NoSuchProcess. + + If timeout (in seconds) is specified and process is still alive + raise TimeoutExpired. + + 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") + return self._proc.wait(timeout) + + +# ===================================================================== +# --- Popen class +# ===================================================================== + + +class Popen(Process): + """A more convenient interface to stdlib subprocess module. + It starts a sub process and deals with it exactly as when using + subprocess.Popen class but in addition also provides all the + properties and methods of psutil.Process class as a unified + interface: + + >>> import psutil + >>> from subprocess import PIPE + >>> p = psutil.Popen(["python", "-c", "print 'hi'"], stdout=PIPE) + >>> p.name() + 'python' + >>> p.uids() + user(real=1000, effective=1000, saved=1000) + >>> p.username() + 'giampaolo' + >>> p.communicate() + ('hi\n', None) + >>> p.terminate() + >>> p.wait(timeout=2) + 0 + >>> + + For method names common to both classes such as kill(), terminate() + and wait(), psutil.Process implementation takes precedence. + + Unlike subprocess.Popen this class pre-emptively checks wheter PID + has been reused on send_signal(), terminate() and kill() so that + you don't accidentally terminate another process, fixing + http://bugs.python.org/issue6973. + + For a complete documentation refer to: + http://docs.python.org/library/subprocess.html + """ + + def __init__(self, *args, **kwargs): + # Explicitly avoid to raise NoSuchProcess in case the process + # spawned by subprocess.Popen terminates too quickly, see: + # https://github.com/giampaolo/psutil/issues/193 + self.__subproc = subprocess.Popen(*args, **kwargs) + self._init(self.__subproc.pid, _ignore_nsp=True) + + def __dir__(self): + return sorted(set(dir(Popen) + dir(subprocess.Popen))) + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except AttributeError: + try: + return object.__getattribute__(self.__subproc, name) + except AttributeError: + raise AttributeError("%s instance has no attribute '%s'" + % (self.__class__.__name__, name)) + + def wait(self, timeout=None): + if self.__subproc.returncode is not None: + return self.__subproc.returncode + ret = super(Popen, self).wait(timeout) + self.__subproc.returncode = ret + return ret + + +# ===================================================================== +# --- system processes related functions +# ===================================================================== + + +def pids(): + """Return a list of current running PIDs.""" + return _psplatform.pids() + + +def pid_exists(pid): + """Return True if given PID exists in the current process list. + This is faster than doing "pid in psutil.pids()" and + should be preferred. + """ + if pid < 0: + return False + elif pid == 0 and _POSIX: + # On POSIX we use os.kill() to determine PID existence. + # According to "man 2 kill" PID 0 has a special meaning + # though: it refers to <<every process in the process + # group of the calling process>> and that is not we want + # to do here. + return pid in pids() + else: + return _psplatform.pid_exists(pid) + + +_pmap = {} + + +def process_iter(): + """Return a generator yielding a Process instance for all + running processes. + + Every new Process instance is only created once and then cached + into an internal table which is updated every time this is used. + + Cached Process instances are checked for identity so that you're + safe in case a PID has been reused by another process, in which + case the cached instance is updated. + + The sorting order in which processes are yielded is based on + their PIDs. + """ + def add(pid): + proc = Process(pid) + _pmap[proc.pid] = proc + return proc + + def remove(pid): + _pmap.pop(pid, None) + + a = set(pids()) + b = set(_pmap.keys()) + new_pids = a - b + gone_pids = b - a + + for pid in gone_pids: + remove(pid) + for pid, proc in sorted(list(_pmap.items()) + + list(dict.fromkeys(new_pids).items())): + 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(): + 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. + yield proc + + +def wait_procs(procs, timeout=None, callback=None): + """Convenience function which waits for a list of processes to + terminate. + + Return a (gone, alive) tuple indicating which processes + are gone and which ones are still alive. + + The gone ones will have a new 'returncode' attribute indicating + process exit status (may be None). + + 'callback' is a function which gets called every time a process + terminates (a Process instance is passed as callback argument). + + Function will return as soon as all processes terminate or when + timeout occurs. + + Typical use case is: + + - send SIGTERM to a list of processes + - give them some time to terminate + - send SIGKILL to those ones which are still alive + + Example: + + >>> def on_terminate(proc): + ... print("process {} terminated".format(proc)) + ... + >>> for p in procs: + ... p.terminate() + ... + >>> gone, alive = wait_procs(procs, timeout=3, callback=on_terminate) + >>> for p in alive: + ... p.kill() + """ + def check_gone(proc, timeout): + try: + returncode = proc.wait(timeout=timeout) + except TimeoutExpired: + pass + else: + if returncode is not None or not proc.is_running(): + proc.returncode = returncode + gone.add(proc) + if callback is not None: + callback(proc) + + if timeout is not None and not timeout >= 0: + msg = "timeout must be a positive integer, got %s" % timeout + raise ValueError(msg) + gone = set() + alive = set(procs) + if callback is not None and not callable(callback): + raise TypeError("callback %r is not a callable" % callable) + if timeout is not None: + deadline = _timer() + timeout + + while alive: + if timeout is not None and timeout <= 0: + break + for proc in alive: + # Make sure that every complete iteration (all processes) + # will last max 1 sec. + # We do this because we don't want to wait too long on a + # single process: in case it terminates too late other + # processes may disappear in the meantime and their PID + # reused. + max_timeout = 1.0 / len(alive) + if timeout is not None: + timeout = min((deadline - _timer()), max_timeout) + if timeout <= 0: + break + check_gone(proc, timeout) + else: + check_gone(proc, max_timeout) + alive = alive - gone + + if alive: + # Last attempt over processes survived so far. + # timeout == 0 won't make this function wait any further. + for proc in alive: + check_gone(proc, 0) + alive = alive - gone + + return (list(gone), list(alive)) + + +# ===================================================================== +# --- CPU related functions +# ===================================================================== + + +@memoize +def cpu_count(logical=True): + """Return the number of logical CPUs in the system (same as + os.cpu_count() in Python 3.4). + + If logical is False return the number of physical cores only + (e.g. hyper thread CPUs are excluded). + + Return None if undetermined. + + The return value is cached after first call. + If desired cache can be cleared like this: + + >>> psutil.cpu_count.cache_clear() + """ + if logical: + return _psplatform.cpu_count_logical() + else: + return _psplatform.cpu_count_physical() + + +def cpu_times(percpu=False): + """Return system-wide CPU times as a namedtuple. + Every CPU time represents the seconds the CPU has spent in the given mode. + The namedtuple's fields availability varies depending on the platform: + - user + - system + - idle + - nice (UNIX) + - iowait (Linux) + - irq (Linux, FreeBSD) + - softirq (Linux) + - steal (Linux >= 2.6.11) + - guest (Linux >= 2.6.24) + - guest_nice (Linux >= 3.2.0) + + When percpu is True return a list of namedtuples for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + """ + if not percpu: + return _psplatform.cpu_times() + else: + return _psplatform.per_cpu_times() + + +_last_cpu_times = cpu_times() +_last_per_cpu_times = cpu_times(percpu=True) + + +def cpu_percent(interval=None, percpu=False): + """Return a float representing the current system-wide CPU + utilization as a percentage. + + When interval is > 0.0 compares system CPU times elapsed before + and after the interval (blocking). + + When interval is 0.0 or None compares system CPU times elapsed + since last call or module import, returning immediately (non + blocking). That means the first time this is called it will + return a meaningless 0.0 value which you should ignore. + In this case is recommended for accuracy that this function be + called with at least 0.1 seconds between calls. + + When percpu is True returns a list of floats representing the + utilization as a percentage for each CPU. + First element of the list refers to first CPU, second element + to second CPU and so on. + The order of the list is consistent across calls. + + Examples: + + >>> # blocking, system-wide + >>> psutil.cpu_percent(interval=1) + 2.0 + >>> + >>> # blocking, per-cpu + >>> psutil.cpu_percent(interval=1, percpu=True) + [2.0, 1.0] + >>> + >>> # non-blocking (percentage since last call) + >>> psutil.cpu_percent(interval=None) + 2.9 + >>> + """ + global _last_cpu_times + global _last_per_cpu_times + blocking = interval is not None and interval > 0.0 + + def calculate(t1, t2): + t1_all = sum(t1) + t1_busy = t1_all - t1.idle + + t2_all = sum(t2) + t2_busy = t2_all - t2.idle + + # this usually indicates a float precision issue + if t2_busy <= t1_busy: + return 0.0 + + busy_delta = t2_busy - t1_busy + all_delta = t2_all - t1_all + busy_perc = (busy_delta / all_delta) * 100 + return round(busy_perc, 1) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times + _last_cpu_times = cpu_times() + return calculate(t1, _last_cpu_times) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times + _last_per_cpu_times = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times): + 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 + + +def cpu_times_percent(interval=None, percpu=False): + """Same as cpu_percent() but provides utilization percentages + for each specific CPU time as is returned by cpu_times(). + For instance, on Linux we'll get: + + >>> cpu_times_percent() + cpupercent(user=4.8, nice=0.0, system=4.8, idle=90.5, iowait=0.0, + irq=0.0, softirq=0.0, steal=0.0, guest=0.0, guest_nice=0.0) + >>> + + interval and percpu arguments have the same meaning as in + cpu_percent(). + """ + global _last_cpu_times_2 + global _last_per_cpu_times_2 + blocking = interval is not None and interval > 0.0 + + def calculate(t1, t2): + nums = [] + all_delta = sum(t2) - sum(t1) + for field in t1._fields: + field_delta = getattr(t2, field) - getattr(t1, field) + try: + field_perc = (100 * field_delta) / all_delta + except ZeroDivisionError: + field_perc = 0.0 + field_perc = round(field_perc, 1) + # CPU times are always supposed to increase over time + # or at least remain the same and that's because time + # cannot go backwards. + # Surprisingly sometimes this might not be the case (at + # least on Windows and Linux), see: + # https://github.com/giampaolo/psutil/issues/392 + # https://github.com/giampaolo/psutil/issues/645 + # I really don't know what to do about that except + # forcing the value to 0 or 100. + if field_perc > 100.0: + field_perc = 100.0 + # `<=` because `-0.0 == 0.0` evaluates to True + elif field_perc <= 0.0: + field_perc = 0.0 + nums.append(field_perc) + return _psplatform.scputimes(*nums) + + # system-wide usage + if not percpu: + if blocking: + t1 = cpu_times() + time.sleep(interval) + else: + t1 = _last_cpu_times_2 + _last_cpu_times_2 = cpu_times() + return calculate(t1, _last_cpu_times_2) + # per-cpu usage + else: + ret = [] + if blocking: + tot1 = cpu_times(percpu=True) + time.sleep(interval) + else: + tot1 = _last_per_cpu_times_2 + _last_per_cpu_times_2 = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times_2): + ret.append(calculate(t1, t2)) + return ret + + +# ===================================================================== +# --- system memory related functions +# ===================================================================== + + +def virtual_memory(): + """Return statistics about system memory usage as a namedtuple + including the following fields, expressed in bytes: + + - total: + total physical memory available. + + - available: + the actual amount of available memory that can be given + instantly to processes that request more memory in bytes; this + is calculated by summing different memory values depending on + the platform (e.g. free + buffers + cached on Linux) and it is + supposed to be used to monitor actual memory usage in a cross + platform fashion. + + - percent: + the percentage usage calculated as (total - available) / total * 100 + + - used: + memory used, calculated differently depending on the platform and + designed for informational purposes only: + OSX: active + inactive + wired + BSD: active + wired + cached + LINUX: total - free + + - free: + memory not being used at all (zeroed) that is readily available; + note that this doesn't reflect the actual memory available + (use 'available' instead) + + Platform-specific fields: + + - active (UNIX): + memory currently in use or very recently used, and so it is in RAM. + + - inactive (UNIX): + memory that is marked as not used. + + - buffers (BSD, Linux): + cache for things like file system metadata. + + - cached (BSD, OSX): + cache for various things. + + - wired (OSX, BSD): + memory that is marked to always stay in RAM. It is never moved to disk. + + - shared (BSD): + memory that may be simultaneously accessed by multiple processes. + + The sum of 'used' and 'available' does not necessarily equal total. + On Windows 'available' and 'free' are the same. + """ + global _TOTAL_PHYMEM + ret = _psplatform.virtual_memory() + # cached for later use in Process.memory_percent() + _TOTAL_PHYMEM = ret.total + return ret + + +def swap_memory(): + """Return system swap memory statistics as a namedtuple including + the following fields: + + - total: total swap memory in bytes + - used: used swap memory in bytes + - free: free swap memory in bytes + - percent: the percentage usage + - sin: no. of bytes the system has swapped in from disk (cumulative) + - sout: no. of bytes the system has swapped out from disk (cumulative) + + 'sin' and 'sout' on Windows are meaningless and always set to 0. + """ + return _psplatform.swap_memory() + + +# ===================================================================== +# --- disks/paritions related functions +# ===================================================================== + + +def disk_usage(path): + """Return disk usage statistics about the given path as a namedtuple + including total, used and free space expressed in bytes plus the + percentage usage. + """ + return _psplatform.disk_usage(path) + + +def disk_partitions(all=False): + """Return mounted partitions as a list of + (device, mountpoint, fstype, opts) namedtuple. + 'opts' field is a raw string separated by commas indicating mount + options which may vary depending on the platform. + + If "all" parameter is False return physical devices only and ignore + all others. + """ + return _psplatform.disk_partitions(all) + + +def disk_io_counters(perdisk=False): + """Return system disk I/O statistics as a namedtuple including + the following fields: + + - read_count: number of reads + - write_count: number of writes + - read_bytes: number of bytes read + - write_bytes: number of bytes written + - read_time: time spent reading from disk (in milliseconds) + - write_time: time spent writing to disk (in milliseconds) + + If perdisk is True return the same information for every + physical disk installed on the system as a dictionary + with partition names as the keys and the namedtuple + described above as the values. + + On recent Windows versions 'diskperf -y' command may need to be + executed first otherwise this function won't find any disk. + """ + rawdict = _psplatform.disk_io_counters() + if not rawdict: + raise RuntimeError("couldn't find any physical disk") + if perdisk: + for disk, fields in rawdict.items(): + rawdict[disk] = _common.sdiskio(*fields) + return rawdict + else: + return _common.sdiskio(*[sum(x) for x in zip(*rawdict.values())]) + + +# ===================================================================== +# --- network related functions +# ===================================================================== + + +def net_io_counters(pernic=False): + """Return network I/O statistics as a namedtuple including + the following fields: + + - bytes_sent: number of bytes sent + - bytes_recv: number of bytes received + - packets_sent: number of packets sent + - packets_recv: number of packets received + - errin: total number of errors while receiving + - errout: total number of errors while sending + - dropin: total number of incoming packets which were dropped + - dropout: total number of outgoing packets which were dropped + (always 0 on OSX and BSD) + + If pernic is True return the same information for every + network interface installed on the system as a dictionary + with network interface names as the keys and the namedtuple + described above as the values. + """ + rawdict = _psplatform.net_io_counters() + if not rawdict: + raise RuntimeError("couldn't find any network interface") + if pernic: + for nic, fields in rawdict.items(): + rawdict[nic] = _common.snetio(*fields) + return rawdict + else: + return _common.snetio(*[sum(x) for x in zip(*rawdict.values())]) + + +def net_connections(kind='inet'): + """Return system-wide connections as a list of + (fd, family, type, laddr, raddr, status, pid) namedtuples. + In case of limited privileges 'fd' and 'pid' may be set to -1 + and None respectively. + The 'kind' parameter filters for connections that fit the + following criteria: + + Kind Value Connections using + inet IPv4 and IPv6 + inet4 IPv4 + inet6 IPv6 + tcp TCP + tcp4 TCP over IPv4 + tcp6 TCP over IPv6 + udp UDP + udp4 UDP over IPv4 + udp6 UDP over IPv6 + unix UNIX socket (both UDP and TCP protocols) + all the sum of all the possible families and protocols + + On OSX this function requires root privileges. + """ + return _psplatform.net_connections(kind) + + +def net_if_addrs(): + """Return the addresses associated to each NIC (network interface + card) installed on the system as a dictionary whose keys are the + NIC names and value is a list of namedtuples for each address + assigned to the NIC. Each namedtuple includes 4 fields: + + - family + - address + - netmask + - broadcast + + 'family' can be either socket.AF_INET, socket.AF_INET6 or + psutil.AF_LINK, which refers to a MAC address. + 'address' is the primary address, 'netmask' and 'broadcast' + may be None. + Note: you can have more than one address of the same family + associated with each interface. + """ + has_enums = sys.version_info >= (3, 4) + if has_enums: + import socket + rawlist = _psplatform.net_if_addrs() + rawlist.sort(key=lambda x: x[1]) # sort by family + ret = collections.defaultdict(list) + for name, fam, addr, mask, broadcast in rawlist: + if has_enums: + try: + fam = socket.AddressFamily(fam) + except ValueError: + if os.name == 'nt' and fam == -1: + fam = _psplatform.AF_LINK + elif (hasattr(_psplatform, "AF_LINK") and + _psplatform.AF_LINK == fam): + # 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 + fam = _psplatform.AF_LINK + ret[name].append(_common.snic(fam, addr, mask, broadcast)) + return dict(ret) + + +def net_if_stats(): + """Return information about each NIC (network interface card) + installed on the system as a dictionary whose keys are the + NIC names and value is a namedtuple with the following fields: + + - isup: whether the interface is up (bool) + - duplex: can be either NIC_DUPLEX_FULL, NIC_DUPLEX_HALF or + NIC_DUPLEX_UNKNOWN + - speed: the NIC speed expressed in mega bits (MB); if it can't + be determined (e.g. 'localhost') it will be set to 0. + - mtu: the maximum transmission unit expressed in bytes. + """ + return _psplatform.net_if_stats() + + +# ===================================================================== +# --- other system related functions +# ===================================================================== + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + # Note: we are not caching this because it is subject to + # system clock updates. + return _psplatform.boot_time() + + +def users(): + """Return users currently connected on the system as a list of + namedtuples including the following fields. + + - user: the name of the user + - terminal: the tty or pseudo-tty associated with the user, if any. + - host: the host name associated with the entry, if any. + - started: the creation time as a floating point number expressed in + seconds since the epoch. + """ + return _psplatform.users() + + +def test(): + """List info of all currently running processes emulating ps aux + output. + """ + import datetime + + today_day = datetime.date.today() + templ = "%-10s %5s %4s %4s %7s %7s %-13s %5s %7s %s" + attrs = ['pid', 'cpu_percent', 'memory_percent', 'name', 'cpu_times', + 'create_time', 'memory_info'] + if _POSIX: + attrs.append('uids') + attrs.append('terminal') + print(templ % ("USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", + "START", "TIME", "COMMAND")) + for p in process_iter(): + try: + pinfo = p.as_dict(attrs, ad_value='') + except NoSuchProcess: + pass + else: + if pinfo['create_time']: + ctime = datetime.datetime.fromtimestamp(pinfo['create_time']) + if ctime.date() == today_day: + ctime = ctime.strftime("%H:%M") + else: + ctime = ctime.strftime("%b%d") + else: + ctime = '' + cputime = time.strftime("%M:%S", + time.localtime(sum(pinfo['cpu_times']))) + try: + user = p.username() + except Error: + user = '' + if _WINDOWS and '\\' in user: + user = user.split('\\')[1] + vms = pinfo['memory_info'] and \ + int(pinfo['memory_info'].vms / 1024) or '?' + rss = pinfo['memory_info'] and \ + int(pinfo['memory_info'].rss / 1024) or '?' + memp = pinfo['memory_percent'] and \ + round(pinfo['memory_percent'], 1) or '?' + print(templ % ( + user[:10], + pinfo['pid'], + pinfo['cpu_percent'], + memp, + vms, + rss, + pinfo.get('terminal', '') or '?', + ctime, + cputime, + pinfo['name'].strip() or '?')) + + +del memoize, division +if sys.version_info < (3, 0): + del num + +if __name__ == "__main__": + test() diff --git a/python/psutil/psutil/_common.py b/python/psutil/psutil/_common.py new file mode 100644 index 0000000000..e9acf595d7 --- /dev/null +++ b/python/psutil/psutil/_common.py @@ -0,0 +1,246 @@ +# /usr/bin/env python + +# 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. + +"""Common objects shared by all _ps* modules.""" + +from __future__ import division +import errno +import functools +import os +import socket +import stat +import sys +from collections import namedtuple +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM +try: + import threading +except ImportError: + import dummy_threading as threading + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +# --- constants + +AF_INET6 = getattr(socket, 'AF_INET6', None) +AF_UNIX = getattr(socket, 'AF_UNIX', None) + +STATUS_RUNNING = "running" +STATUS_SLEEPING = "sleeping" +STATUS_DISK_SLEEP = "disk-sleep" +STATUS_STOPPED = "stopped" +STATUS_TRACING_STOP = "tracing-stop" +STATUS_ZOMBIE = "zombie" +STATUS_DEAD = "dead" +STATUS_WAKE_KILL = "wake-kill" +STATUS_WAKING = "waking" +STATUS_IDLE = "idle" # BSD +STATUS_LOCKED = "locked" # BSD +STATUS_WAITING = "waiting" # BSD + +CONN_ESTABLISHED = "ESTABLISHED" +CONN_SYN_SENT = "SYN_SENT" +CONN_SYN_RECV = "SYN_RECV" +CONN_FIN_WAIT1 = "FIN_WAIT1" +CONN_FIN_WAIT2 = "FIN_WAIT2" +CONN_TIME_WAIT = "TIME_WAIT" +CONN_CLOSE = "CLOSE" +CONN_CLOSE_WAIT = "CLOSE_WAIT" +CONN_LAST_ACK = "LAST_ACK" +CONN_LISTEN = "LISTEN" +CONN_CLOSING = "CLOSING" +CONN_NONE = "NONE" + +if enum is None: + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 +else: + class NicDuplex(enum.IntEnum): + NIC_DUPLEX_FULL = 2 + NIC_DUPLEX_HALF = 1 + NIC_DUPLEX_UNKNOWN = 0 + + globals().update(NicDuplex.__members__) + + +# --- functions + +def usage_percent(used, total, _round=None): + """Calculate percentage usage of 'used' against 'total'.""" + try: + ret = (used / total) * 100 + except ZeroDivisionError: + ret = 0 + if _round is not None: + return round(ret, _round) + else: + return ret + + +def memoize(fun): + """A simple memoize decorator for functions supporting (hashable) + positional arguments. + It also provides a cache_clear() function for clearing the cache: + + >>> @memoize + ... def foo() + ... return 1 + ... + >>> foo() + 1 + >>> foo.cache_clear() + >>> + """ + @functools.wraps(fun) + def wrapper(*args, **kwargs): + key = (args, frozenset(sorted(kwargs.items()))) + lock.acquire() + try: + try: + return cache[key] + except KeyError: + ret = cache[key] = fun(*args, **kwargs) + finally: + lock.release() + return ret + + def cache_clear(): + """Clear cache.""" + lock.acquire() + try: + cache.clear() + finally: + lock.release() + + lock = threading.RLock() + cache = {} + wrapper.cache_clear = cache_clear + return wrapper + + +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 + """ + try: + st = os.stat(path) + except OSError as err: + if err.errno in (errno.EPERM, errno.EACCES): + raise + return False + else: + return stat.S_ISREG(st.st_mode) + + +def sockfam_to_enum(num): + """Convert a numeric socket family value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + try: + return socket.AddressFamily(num) + except (ValueError, AttributeError): + return num + + +def socktype_to_enum(num): + """Convert a numeric socket type value to an IntEnum member. + If it's not a known member, return the numeric value itself. + """ + if enum is None: + return num + try: + return socket.AddressType(num) + except (ValueError, AttributeError): + return num + + +# --- Process.connections() 'kind' parameter mapping + +conn_tmap = { + "all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + "tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]), + "tcp4": ([AF_INET], [SOCK_STREAM]), + "udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]), + "udp4": ([AF_INET], [SOCK_DGRAM]), + "inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), + "inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]), + "inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]), +} + +if AF_INET6 is not None: + conn_tmap.update({ + "tcp6": ([AF_INET6], [SOCK_STREAM]), + "udp6": ([AF_INET6], [SOCK_DGRAM]), + }) + +if AF_UNIX is not None: + conn_tmap.update({ + "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), + }) + +del AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM + + +# --- namedtuples for psutil.* system-related functions + +# psutil.swap_memory() +sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin', + 'sout']) +# psutil.disk_usage() +sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent']) +# psutil.disk_io_counters() +sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', + 'read_bytes', 'write_bytes', + 'read_time', 'write_time']) +# psutil.disk_partitions() +sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts']) +# psutil.net_io_counters() +snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv', + 'packets_sent', 'packets_recv', + 'errin', 'errout', + 'dropin', 'dropout']) +# psutil.users() +suser = namedtuple('suser', ['name', 'terminal', 'host', 'started']) +# psutil.net_connections() +sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', + 'status', 'pid']) +# psutil.net_if_addrs() +snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast']) +# psutil.net_if_stats() +snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) + + +# --- namedtuples for psutil.Process methods + +# psutil.Process.memory_info() +pmem = namedtuple('pmem', ['rss', 'vms']) +# psutil.Process.cpu_times() +pcputimes = namedtuple('pcputimes', ['user', 'system']) +# psutil.Process.open_files() +popenfile = namedtuple('popenfile', ['path', 'fd']) +# psutil.Process.threads() +pthread = namedtuple('pthread', ['id', 'user_time', 'system_time']) +# psutil.Process.uids() +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']) +# 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']) diff --git a/python/psutil/psutil/_compat.py b/python/psutil/psutil/_compat.py new file mode 100644 index 0000000000..38744a84ae --- /dev/null +++ b/python/psutil/psutil/_compat.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python + +# 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. + +"""Module which provides compatibility with older Python versions.""" + +import collections +import functools +import sys + +__all__ = ["PY3", "long", "xrange", "unicode", "callable", "lru_cache"] + +PY3 = sys.version_info[0] == 3 + +if PY3: + long = int + xrange = range + unicode = str + + def u(s): + return s +else: + long = long + xrange = xrange + unicode = unicode + + def u(s): + return unicode(s, "unicode_escape") + + +# removed in 3.0, reintroduced in 3.2 +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +# --- stdlib additions + + +# py 3.2 functools.lru_cache +# Taken from: http://code.activestate.com/recipes/578078 +# Credit: Raymond Hettinger +try: + from functools import lru_cache +except ImportError: + try: + from threading import RLock + except ImportError: + from dummy_threading import RLock + + _CacheInfo = collections.namedtuple( + "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + + class _HashedSeq(list): + __slots__ = 'hashvalue' + + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) + + 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): + key = args + if kwds: + sorted_items = sorted(kwds.items()) + key += kwd_mark + for item in sorted_items: + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for k, v in sorted_items) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + + def lru_cache(maxsize=100, typed=False): + """Least-recently-used cache decorator, see: + http://docs.python.org/3/library/functools.html#functools.lru_cache + """ + def decorating_function(user_function): + cache = dict() + stats = [0, 0] + HITS, MISSES = 0, 1 + make_key = _make_key + cache_get = cache.get + _len = len + lock = RLock() + root = [] + root[:] = [root, root, None, None] + 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) + if result is not root: + stats[HITS] += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + stats[MISSES] += 1 + return result + else: + def wrapper(*args, **kwds): + if kwds or typed: + key = make_key(args, kwds, typed) + else: + key = args + lock.acquire() + try: + link = cache_get(key) + if link is not None: + root, = nonlocal_root + link_prev, link_next, key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + stats[HITS] += 1 + return result + finally: + lock.release() + result = user_function(*args, **kwds) + lock.acquire() + try: + root, = nonlocal_root + if key in cache: + pass + elif _len(cache) >= maxsize: + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + root = nonlocal_root[0] = oldroot[NEXT] + oldkey = root[KEY] + root[KEY] = root[RESULT] = None + del cache[oldkey] + cache[key] = oldroot + else: + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + stats[MISSES] += 1 + finally: + lock.release() + return result + + def cache_info(): + """Report cache statistics""" + lock.acquire() + try: + return _CacheInfo(stats[HITS], stats[MISSES], maxsize, + len(cache)) + finally: + lock.release() + + def cache_clear(): + """Clear the cache and cache statistics""" + lock.acquire() + try: + cache.clear() + root = nonlocal_root[0] + root[:] = [root, root, None, None] + stats[:] = [0, 0] + finally: + lock.release() + + wrapper.__wrapped__ = user_function + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return functools.update_wrapper(wrapper, user_function) + + return decorating_function diff --git a/python/psutil/psutil/_psbsd.py b/python/psutil/psutil/_psbsd.py new file mode 100644 index 0000000000..db54a02e13 --- /dev/null +++ b/python/psutil/psutil/_psbsd.py @@ -0,0 +1,455 @@ +#!/usr/bin/env python + +# 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. + +"""FreeBSD platform implementation.""" + +import errno +import functools +import os +import xml.etree.ElementTree as ET +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_bsd as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap, usage_percent, sockfam_to_enum +from ._common import socktype_to_enum + + +__extra__all__ = [] + +# --- constants + +PROC_STATUSES = { + cext.SSTOP: _common.STATUS_STOPPED, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SIDL: _common.STATUS_IDLE, + cext.SWAIT: _common.STATUS_WAITING, + cext.SLOCK: _common.STATUS_LOCKED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +# extend base mem ntuple with BSD-specific memory metrics +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached', 'shared', 'wired']) +scputimes = namedtuple( + 'scputimes', ['user', 'nice', 'system', 'idle', 'irq']) +pextmem = namedtuple('pextmem', ['rss', 'vms', 'text', 'data', 'stack']) +pmmap_grouped = namedtuple( + 'pmmap_grouped', 'path rss, private, ref_count, shadow_count') +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count') + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + total, free, active, inactive, wired, cached, buffers, shared = mem + 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) + + +def swap_memory(): + """System swap memory as (total, used, free, sin, sout) namedtuple.""" + total, used, free, sin, sout = [x * PAGESIZE for x in cext.swap_mem()] + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +def cpu_times(): + """Return system per-CPU times as a namedtuple""" + user, nice, system, idle, irq = cext.cpu_times() + return scputimes(user, nice, system, idle, irq) + + +if hasattr(cext, "per_cpu_times"): + def per_cpu_times(): + """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. + # On FreeBSD < 8 we cannot gather per-cpu information, see: + # https://github.com/giampaolo/psutil/issues/226 + # If num cpus > 1, on first call we return single cpu times to avoid a + # crash at psutil import time. + # Next calls will fail with NotImplementedError + def per_cpu_times(): + if cpu_count_logical() == 1: + return [cpu_times()] + if per_cpu_times.__called__: + raise NotImplementedError("supported only starting from FreeBSD 8") + per_cpu_times.__called__ = True + return [cpu_times()] + + per_cpu_times.__called__ = False + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs 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() + 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) + 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 cpu_count_logical() == 1: + return 1 + return ret + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def disk_partitions(all=False): + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def users(): + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp = item + if tty == '~': + continue # reboot or shutdown + nt = _common.suser(user, tty or None, hostname, tstamp) + retlist.append(nt) + return retlist + + +def net_connections(kind): + 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]))) + families, types = conn_tmap[kind] + ret = set() + rawlist = cext.net_connections() + 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: + try: + status = TCP_STATUSES[status] + except KeyError: + # XXX: Not sure why this happens. I saw this occurring + # with IPv6 sockets opened by 'vim'. Those sockets + # have a very short lifetime so maybe the kernel + # can't initialize their status? + status = TCP_STATUSES[cext.PSUTIL_CONN_NONE] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext_posix.net_if_stats(name) + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +pids = cext.pids +pid_exists = _psposix.pid_exists +disk_usage = _psposix.disk_usage +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + if err.errno == errno.ESRCH: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def terminal(self): + tty_nr = cext.proc_tty_nr(self.pid) + tmap = _psposix._get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def ppid(self): + return cext.proc_ppid(self.pid) + + @wrap_exceptions + def uids(self): + real, effective, saved = cext.proc_uids(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + real, effective, saved = cext.proc_gids(self.pid) + return _common.pgids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def memory_info(self): + rss, vms = cext.proc_memory_info(self.pid)[:2] + return _common.pmem(rss, vms) + + @wrap_exceptions + def memory_info_ex(self): + return pextmem(*cext.proc_memory_info(self.pid)) + + @wrap_exceptions + def create_time(self): + return cext.proc_create_time(self.pid) + + @wrap_exceptions + def num_threads(self): + return cext.proc_num_threads(self.pid) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @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]))) + 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 + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + status = TCP_STATUSES[status] + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.append(nt) + return ret + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = cext.proc_status(self.pid) + if code in PROC_STATUSES: + return PROC_STATUSES[code] + # XXX is this legit? will we even ever get here? + return "?" + + @wrap_exceptions + def io_counters(self): + rc, wc, rb, wb = cext.proc_io_counters(self.pid) + return _common.pio(rc, wc, rb, wb) + + nt_mmap_grouped = namedtuple( + 'mmap', 'path rss, private, ref_count, shadow_count') + nt_mmap_ext = namedtuple( + 'mmap', 'addr, perms path rss, private, ref_count, shadow_count') + + # FreeBSD < 8 does not support functions based on kinfo_getfile() + # and kinfo_getvmmap() + if hasattr(cext, '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] + + @wrap_exceptions + def cwd(self): + """Return process current working directory.""" + # sometimes we get an empty string, in which case we turn + # it into None + return cext.proc_cwd(self.pid) or None + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) + + @wrap_exceptions + def num_fds(self): + """Return the number of file descriptors opened by this process.""" + return cext.proc_num_fds(self.pid) + + else: + def _not_implemented(self): + raise NotImplementedError("supported only starting from FreeBSD 8") + + open_files = _not_implemented + proc_cwd = _not_implemented + memory_maps = _not_implemented + num_fds = _not_implemented + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + # Pre-emptively check if CPUs are valid because the C + # function has a weird behavior in case of invalid CPUs, + # see: https://github.com/giampaolo/psutil/issues/586 + 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)) + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + # 'man cpuset_setaffinity' about EDEADLK: + # <<the call would leave a thread without a valid CPU to run + # on because the set does not overlap with the thread's + # anonymous mask>> + if err.errno in (errno.EINVAL, errno.EDEADLK): + for cpu in cpus: + if cpu not in allcpus: + raise ValueError("invalid CPU #%i (choose between %s)" + % (cpu, allcpus)) + raise diff --git a/python/psutil/psutil/_pslinux.py b/python/psutil/psutil/_pslinux.py new file mode 100644 index 0000000000..7eb25f519c --- /dev/null +++ b/python/psutil/psutil/_pslinux.py @@ -0,0 +1,1206 @@ +#!/usr/bin/env python + +# 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. + +"""Linux platform implementation.""" + +from __future__ import division + +import base64 +import errno +import functools +import os +import re +import socket +import struct +import sys +import warnings +from collections import namedtuple, defaultdict + +from . import _common +from . import _psposix +from . import _psutil_linux as cext +from . import _psutil_posix as cext_posix +from ._common import isfile_strict, usage_percent +from ._common import NIC_DUPLEX_FULL, NIC_DUPLEX_HALF, NIC_DUPLEX_UNKNOWN +from ._compat import PY3, long + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +__extra__all__ = [ + # io prio constants + "IOPRIO_CLASS_NONE", "IOPRIO_CLASS_RT", "IOPRIO_CLASS_BE", + "IOPRIO_CLASS_IDLE", + # 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", ] + +# --- constants + +HAS_PRLIMIT = hasattr(cext, "linux_prlimit") + +# RLIMIT_* constants, not guaranteed to be present on all kernels +if HAS_PRLIMIT: + for name in dir(cext): + if name.startswith('RLIM'): + __extra__all__.append(name) + +# Number of clock ticks per second +CLOCK_TICKS = os.sysconf("SC_CLK_TCK") +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +BOOT_TIME = None # set later +DEFAULT_ENCODING = sys.getdefaultencoding() +if enum is None: + AF_LINK = socket.AF_PACKET +else: + AddressFamily = enum.IntEnum('AddressFamily', + {'AF_LINK': socket.AF_PACKET}) + AF_LINK = AddressFamily.AF_LINK + +# ioprio_* constants http://linux.die.net/man/2/ioprio_get +if enum is None: + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 +else: + class IOPriority(enum.IntEnum): + IOPRIO_CLASS_NONE = 0 + IOPRIO_CLASS_RT = 1 + IOPRIO_CLASS_BE = 2 + IOPRIO_CLASS_IDLE = 3 + + globals().update(IOPriority.__members__) + +# taken from /fs/proc/array.c +PROC_STATUSES = { + "R": _common.STATUS_RUNNING, + "S": _common.STATUS_SLEEPING, + "D": _common.STATUS_DISK_SLEEP, + "T": _common.STATUS_STOPPED, + "t": _common.STATUS_TRACING_STOP, + "Z": _common.STATUS_ZOMBIE, + "X": _common.STATUS_DEAD, + "x": _common.STATUS_DEAD, + "K": _common.STATUS_WAKE_KILL, + "W": _common.STATUS_WAKING +} + +# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +TCP_STATUSES = { + "01": _common.CONN_ESTABLISHED, + "02": _common.CONN_SYN_SENT, + "03": _common.CONN_SYN_RECV, + "04": _common.CONN_FIN_WAIT1, + "05": _common.CONN_FIN_WAIT2, + "06": _common.CONN_TIME_WAIT, + "07": _common.CONN_CLOSE, + "08": _common.CONN_CLOSE_WAIT, + "09": _common.CONN_LAST_ACK, + "0A": _common.CONN_LISTEN, + "0B": _common.CONN_CLOSING +} + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# --- named tuples + +def _get_cputimes_fields(): + """Return a namedtuple of variable fields depending on the + CPU times available on this Linux kernel version which may be: + (user, nice, system, idle, iowait, irq, softirq, [steal, [guest, + [guest_nice]]]) + """ + with open('/proc/stat', 'rb') as f: + values = f.readline().split()[1:] + fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq'] + vlen = len(values) + if vlen >= 8: + # Linux >= 2.6.11 + fields.append('steal') + if vlen >= 9: + # Linux >= 2.6.24 + fields.append('guest') + if vlen >= 10: + # Linux >= 3.2.0 + fields.append('guest_nice') + return fields + + +scputimes = namedtuple('scputimes', _get_cputimes_fields()) + +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'buffers', 'cached']) + +pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty') + +pmmap_grouped = namedtuple( + 'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean', + 'shared_dirty', 'private_clean', 'private_dirty', + 'referenced', 'anonymous', 'swap']) + +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + + +# --- system memory + +def virtual_memory(): + total, free, buffers, shared, _, _ = cext.linux_sysinfo() + cached = active = inactive = None + with open('/proc/meminfo', 'rb') as f: + for line in f: + if line.startswith(b"Cached:"): + cached = int(line.split()[1]) * 1024 + elif line.startswith(b"Active:"): + active = int(line.split()[1]) * 1024 + elif line.startswith(b"Inactive:"): + inactive = int(line.split()[1]) * 1024 + if (cached is not None and + active is not None and + inactive is not None): + break + else: + # we might get here when dealing with exotic Linux flavors, see: + # https://github.com/giampaolo/psutil/issues/313 + msg = "'cached', 'active' and 'inactive' memory stats couldn't " \ + "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning) + cached = active = inactive = 0 + avail = free + buffers + cached + used = total - free + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, buffers, cached) + + +def swap_memory(): + _, _, _, _, total, free = cext.linux_sysinfo() + used = total - free + percent = usage_percent(used, total, _round=1) + # get pgin/pgouts + with open("/proc/vmstat", "rb") as f: + sin = sout = None + for line in f: + # values are expressed in 4 kilo bytes, we want bytes instead + if line.startswith(b'pswpin'): + sin = int(line.split(b' ')[1]) * 4 * 1024 + elif line.startswith(b'pswpout'): + sout = int(line.split(b' ')[1]) * 4 * 1024 + if sin is not None and sout is not None: + break + else: + # 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) + sin = sout = 0 + return _common.sswap(total, used, free, percent, sin, sout) + + +# --- CPUs + +def cpu_times(): + """Return a named tuple representing the following system-wide + CPU times: + (user, nice, system, idle, iowait, irq, softirq [steal, [guest, + [guest_nice]]]) + Last 3 fields may not be available on all Linux kernel versions. + """ + with open('/proc/stat', 'rb') as f: + values = f.readline().split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + return scputimes(*fields) + + +def per_cpu_times(): + """Return a list of namedtuple representing the CPU times + for every CPU available on the system. + """ + cpus = [] + with open('/proc/stat', 'rb') as f: + # get rid of the first line which refers to system wide CPU stats + f.readline() + for line in f: + if line.startswith(b'cpu'): + values = line.split() + fields = values[1:len(scputimes._fields) + 1] + fields = [float(x) / CLOCK_TICKS for x in fields] + entry = scputimes(*fields) + cpus.append(entry) + return cpus + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # as a second fallback we try to parse /proc/cpuinfo + num = 0 + with open('/proc/cpuinfo', 'rb') as f: + for line in f: + if line.lower().startswith(b'processor'): + num += 1 + + # unknown format (e.g. amrel/sparc architectures), see: + # https://github.com/giampaolo/psutil/issues/200 + # try to parse /proc/stat as a last resort + if num == 0: + search = re.compile('cpu\d') + with open('/proc/stat', 'rt') as f: + for line in f: + line = line.split(' ')[0] + if search.match(line): + num += 1 + + if num == 0: + # mimic os.cpu_count() + return None + return num + + +def cpu_count_physical(): + """Return the number of physical cores in the system.""" + mapping = {} + current_info = {} + with open('/proc/cpuinfo', 'rb') as f: + for line in f: + line = line.strip().lower() + if not line: + # new section + if (b'physical id' in current_info and + b'cpu cores' in current_info): + mapping[current_info[b'physical id']] = \ + current_info[b'cpu cores'] + current_info = {} + else: + # ongoing section + if (line.startswith(b'physical id') or + line.startswith(b'cpu cores')): + key, value = line.split(b'\t:', 1) + current_info[key] = int(value) + + # mimic os.cpu_count() + return sum(mapping.values()) or None + + +# --- other system functions + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp, user_process = 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 == ':0.0' or hostname == ':0': + hostname = 'localhost' + nt = _common.suser(user, tty or None, hostname, tstamp) + retlist.append(nt) + return retlist + + +def boot_time(): + """Return the system boot time expressed in seconds since the epoch.""" + global BOOT_TIME + with open('/proc/stat', 'rb') as f: + for line in f: + if line.startswith(b'btime'): + ret = float(line.strip().split()[1]) + BOOT_TIME = ret + return ret + raise RuntimeError("line 'btime' not found in /proc/stat") + + +# --- processes + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir(b'/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check For the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +# --- network + +class Connections: + """A wrapper on top of /proc/net/* files, retrieving per-process + and system-wide open connections (TCP, UDP, UNIX) similarly to + "netstat -an". + + Note: in case of UNIX sockets we're only able to determine the + local endpoint/path, not the one it's connected to. + According to [1] it would be possible but not easily. + + [1] http://serverfault.com/a/417946 + """ + + def __init__(self): + tcp4 = ("tcp", socket.AF_INET, socket.SOCK_STREAM) + tcp6 = ("tcp6", socket.AF_INET6, socket.SOCK_STREAM) + udp4 = ("udp", socket.AF_INET, socket.SOCK_DGRAM) + udp6 = ("udp6", socket.AF_INET6, socket.SOCK_DGRAM) + unix = ("unix", socket.AF_UNIX, None) + self.tmap = { + "all": (tcp4, tcp6, udp4, udp6, unix), + "tcp": (tcp4, tcp6), + "tcp4": (tcp4,), + "tcp6": (tcp6,), + "udp": (udp4, udp6), + "udp4": (udp4,), + "udp6": (udp6,), + "unix": (unix,), + "inet": (tcp4, tcp6, udp4, udp6), + "inet4": (tcp4, udp4), + "inet6": (tcp6, udp6), + } + + def get_proc_inodes(self, pid): + inodes = defaultdict(list) + for fd in os.listdir("/proc/%s/fd" % pid): + try: + inode = os.readlink("/proc/%s/fd/%s" % (pid, fd)) + except OSError as err: + # ENOENT == file which is gone in the meantime; + # os.stat('/proc/%s' % self.pid) will be done later + # to force NSP (if it's the case) + if err.errno in (errno.ENOENT, errno.ESRCH): + continue + elif err.errno == errno.EINVAL: + # not a link + continue + else: + raise + else: + if inode.startswith('socket:['): + # the process is using a socket + inode = inode[8:][:-1] + inodes[inode].append((pid, int(fd))) + return inodes + + def get_all_inodes(self): + inodes = {} + for pid in pids(): + try: + inodes.update(self.get_proc_inodes(pid)) + except OSError as err: + # os.listdir() is gonna raise a lot of access denied + # exceptions in case of unprivileged user; that's fine + # as we'll just end up returning a connection with PID + # and fd set to None anyway. + # Both netstat -an and lsof does the same so it's + # unlikely we can do any better. + # ENOENT just means a PID disappeared on us. + if err.errno not in ( + errno.ENOENT, errno.ESRCH, errno.EPERM, errno.EACCES): + raise + return inodes + + def decode_address(self, addr, family): + """Accept an "ip:port" address as displayed in /proc/net/* + and convert it into a human readable form, like: + + "0500000A:0016" -> ("10.0.0.5", 22) + "0000000000000000FFFF00000100007F:9E49" -> ("::ffff:127.0.0.1", 40521) + + The IP address portion is a little or big endian four-byte + hexadecimal number; that is, the least significant byte is listed + first, so we need to reverse the order of the bytes to convert it + to an IP address. + The port is represented as a two-byte hexadecimal number. + + Reference: + http://linuxdevcenter.com/pub/a/linux/2000/11/16/LinuxAdmin.html + """ + ip, port = addr.split(':') + port = int(port, 16) + # this usually refers to a local socket in listen mode with + # no end-points connected + if not port: + return () + if PY3: + ip = ip.encode('ascii') + if family == socket.AF_INET: + # see: https://github.com/giampaolo/psutil/issues/201 + if sys.byteorder == 'little': + ip = socket.inet_ntop(family, base64.b16decode(ip)[::-1]) + else: + ip = socket.inet_ntop(family, base64.b16decode(ip)) + else: # IPv6 + # old version - let's keep it, just in case... + # ip = ip.decode('hex') + # return socket.inet_ntop(socket.AF_INET6, + # ''.join(ip[i:i+4][::-1] for i in xrange(0, 16, 4))) + ip = base64.b16decode(ip) + # see: https://github.com/giampaolo/psutil/issues/201 + if sys.byteorder == 'little': + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('>4I', *struct.unpack('<4I', ip))) + else: + ip = socket.inet_ntop( + socket.AF_INET6, + struct.pack('<4I', *struct.unpack('<4I', ip))) + return (ip, port) + + def process_inet(self, file, family, type_, inodes, filter_pid=None): + """Parse /proc/net/tcp* and /proc/net/udp* files.""" + if file.endswith('6') and not os.path.exists(file): + # IPv6 not supported + return + with open(file, 'rt') as f: + f.readline() # skip the first line + for line in f: + try: + _, laddr, raddr, status, _, _, _, _, _, inode = \ + line.split()[:10] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, 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 " + # "PIDs references") + pid, fd = inodes[inode][0] + else: + pid, fd = None, -1 + if filter_pid is not None and filter_pid != pid: + continue + else: + if type_ == socket.SOCK_STREAM: + status = TCP_STATUSES[status] + else: + status = _common.CONN_NONE + laddr = self.decode_address(laddr, family) + raddr = self.decode_address(raddr, family) + yield (fd, family, type_, laddr, raddr, status, pid) + + def process_unix(self, file, family, inodes, filter_pid=None): + """Parse /proc/net/unix files.""" + with open(file, 'rt') as f: + f.readline() # skip the first line + for line in f: + tokens = line.split() + try: + _, _, _, _, type_, _, inode = tokens[0:7] + except ValueError: + raise RuntimeError( + "error while parsing %s; malformed line %r" % ( + file, line)) + if inode in inodes: + # With UNIX sockets we can have a single inode + # referencing many file descriptors. + pairs = inodes[inode] + else: + pairs = [(None, -1)] + for pid, fd in pairs: + if filter_pid is not None and filter_pid != pid: + continue + else: + if len(tokens) == 8: + path = tokens[-1] + else: + path = "" + type_ = int(type_) + raddr = None + status = _common.CONN_NONE + yield (fd, family, type_, path, raddr, status, pid) + + 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]))) + if pid is not None: + inodes = self.get_proc_inodes(pid) + if not inodes: + # no connections for this process + return [] + else: + inodes = self.get_all_inodes() + ret = set() + for f, family, type_ in self.tmap[kind]: + if family in (socket.AF_INET, socket.AF_INET6): + ls = self.process_inet( + "/proc/net/%s" % f, family, type_, inodes, filter_pid=pid) + else: + ls = self.process_unix( + "/proc/net/%s" % f, 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) + else: + conn = _common.sconn(fd, family, type_, laddr, raddr, + status, bound_pid) + ret.add(conn) + return list(ret) + + +_connections = Connections() + + +def net_connections(kind='inet'): + """Return system-wide open connections.""" + return _connections.retrieve(kind) + + +def net_io_counters(): + """Return network I/O statistics for every network interface + installed on the system as a dict of raw tuples. + """ + with open("/proc/net/dev", "rt") as f: + lines = f.readlines() + retdict = {} + for line in lines[2:]: + colon = line.rfind(':') + assert colon > 0, repr(line) + name = line[:colon].strip() + fields = line[colon + 1:].strip().split() + bytes_recv = int(fields[0]) + packets_recv = int(fields[1]) + errin = int(fields[2]) + dropin = int(fields[3]) + bytes_sent = int(fields[8]) + packets_sent = int(fields[9]) + errout = int(fields[10]) + dropout = int(fields[11]) + 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} + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext.net_if_stats(name) + duplex = duplex_map[duplex] + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +net_if_addrs = cext_posix.net_if_addrs + + +# --- disks + +def disk_io_counters(): + """Return disk I/O statistics for every disk installed on the + system as a dict of raw tuples. + """ + # man iostat states that sectors are equivalent with blocks and + # have a size of 512 bytes since 2.4 kernels. This value is + # needed to calculate the amount of disk I/O in bytes. + SECTOR_SIZE = 512 + + # determine partitions we want to look for + partitions = [] + with open("/proc/partitions", "rt") as f: + lines = f.readlines()[2:] + for line in reversed(lines): + _, _, _, name = line.split() + if name[-1].isdigit(): + # we're dealing with a partition (e.g. 'sda1'); 'sda' will + # also be around but we want to omit it + partitions.append(name) + else: + if not partitions or not partitions[-1].startswith(name): + # we're dealing with a disk entity for which no + # partitions have been defined (e.g. 'sda' but + # 'sda1' was not around), see: + # https://github.com/giampaolo/psutil/issues/338 + partitions.append(name) + # + retdict = {} + with open("/proc/diskstats", "rt") as f: + lines = f.readlines() + for line in lines: + # http://www.mjmwired.net/kernel/Documentation/iostats.txt + fields = line.split() + if len(fields) > 7: + _, _, name, reads, _, rbytes, rtime, writes, _, wbytes, wtime = \ + fields[:11] + else: + # from kernel 2.6.0 to 2.6.25 + _, _, name, reads, rbytes, writes, wbytes = fields + rtime, wtime = 0, 0 + if name in partitions: + rbytes = int(rbytes) * SECTOR_SIZE + wbytes = int(wbytes) * SECTOR_SIZE + reads = int(reads) + writes = int(writes) + rtime = int(rtime) + wtime = int(wtime) + retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime) + return retdict + + +def disk_partitions(all=False): + """Return mounted disk partitions as a list of namedtuples""" + fstypes = set() + with open("/proc/filesystems", "r") 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") + + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if device == '' or fstype not in fstypes: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +disk_usage = _psposix.disk_usage + + +# --- decorators + +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: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if NoSuchProcess is None or AccessDenied is None: + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + raise NoSuchProcess(self.pid, self._name) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +def wrap_exceptions_w_zombie(fun): + """Same as above but also handles zombies.""" + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return wrap_exceptions(fun)(self) + except NoSuchProcess: + if not pid_exists(self.pid): + raise + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + return wrapper + + +class Process(object): + """Linux process implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + fname = "/proc/%s/stat" % self.pid + kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict() + with open(fname, "rt", **kw) as f: + data = f.read() + # XXX - gets changed later and probably needs refactoring + return data[data.find('(') + 1:data.rfind(')')] + + def exe(self): + try: + exe = os.readlink("/proc/%s/exe" % self.pid) + except OSError as err: + if err.errno in (errno.ENOENT, errno.ESRCH): + # 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("/proc/%s" % 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) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + + # readlink() might return paths containing null bytes ('\x00'). + # Certain names have ' (deleted)' appended. Usually this is + # bogus as the file actually exists. Either way that's not + # important as we don't want to discriminate executables which + # have been deleted. + exe = exe.split('\x00')[0] + if exe.endswith(' (deleted)') and not os.path.exists(exe): + exe = exe[:-10] + return exe + + @wrap_exceptions + def cmdline(self): + fname = "/proc/%s/cmdline" % self.pid + kw = dict(encoding=DEFAULT_ENCODING) if PY3 else dict() + with open(fname, "rt", **kw) as f: + data = f.read() + if data.endswith('\x00'): + data = data[:-1] + return [x for x in data.split('\x00')] + + @wrap_exceptions + def terminal(self): + tmap = _psposix._get_terminal_map() + with open("/proc/%s/stat" % self.pid, 'rb') as f: + tty_nr = int(f.read().split(b' ')[6]) + try: + return tmap[tty_nr] + except KeyError: + return None + + if os.path.exists('/proc/%s/io' % os.getpid()): + @wrap_exceptions + def io_counters(self): + fname = "/proc/%s/io" % self.pid + with open(fname, 'rb') as f: + rcount = wcount = rbytes = wbytes = None + for line in f: + if rcount is None and line.startswith(b"syscr"): + rcount = int(line.split()[1]) + elif wcount is None and line.startswith(b"syscw"): + wcount = int(line.split()[1]) + elif rbytes is None and line.startswith(b"read_bytes"): + rbytes = int(line.split()[1]) + elif wbytes is None and line.startswith(b"write_bytes"): + wbytes = int(line.split()[1]) + for x in (rcount, wcount, rbytes, wbytes): + if x is None: + raise NotImplementedError( + "couldn't read all necessary info from %r" % fname) + return _common.pio(rcount, wcount, rbytes, wbytes) + else: + def io_counters(self): + raise NotImplementedError("couldn't find /proc/%s/io (kernel " + "too old?)" % self.pid) + + @wrap_exceptions + def cpu_times(self): + with open("/proc/%s/stat" % self.pid, 'rb') as f: + st = f.read().strip() + # ignore the first two values ("pid (exe)") + st = st[st.find(b')') + 2:] + values = st.split(b' ') + utime = float(values[11]) / CLOCK_TICKS + stime = float(values[12]) / CLOCK_TICKS + return _common.pcputimes(utime, stime) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def create_time(self): + with open("/proc/%s/stat" % self.pid, 'rb') as f: + st = f.read().strip() + # ignore the first two values ("pid (exe)") + st = st[st.rfind(b')') + 2:] + values = st.split(b' ') + # According to documentation, starttime is in field 21 and the + # unit is jiffies (clock ticks). + # We first divide it for clock ticks and then add uptime returning + # seconds since the epoch, in UTC. + # Also use cached value if available. + bt = BOOT_TIME or boot_time() + return (float(values[19]) / CLOCK_TICKS) + bt + + @wrap_exceptions + def memory_info(self): + with open("/proc/%s/statm" % self.pid, 'rb') as f: + vms, rss = f.readline().split()[:2] + return _common.pmem(int(rss) * PAGESIZE, + int(vms) * PAGESIZE) + + @wrap_exceptions + def memory_info_ex(self): + # ============================================================ + # | FIELD | DESCRIPTION | AKA | TOP | + # ============================================================ + # | rss | resident set size | | RES | + # | vms | total program size | size | VIRT | + # | shared | shared pages (from shared mappings) | | SHR | + # | text | text ('code') | trs | CODE | + # | lib | library (unused in Linux 2.6) | lrs | | + # | data | data + stack | drs | DATA | + # | dirty | dirty pages (unused in Linux 2.6) | dt | | + # ============================================================ + with open("/proc/%s/statm" % self.pid, "rb") as f: + vms, rss, shared, text, lib, data, dirty = \ + [int(x) * PAGESIZE for x in f.readline().split()[:7]] + return pextmem(rss, vms, shared, text, lib, data, dirty) + + if os.path.exists('/proc/%s/smaps' % os.getpid()): + + @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 + """ + with open("/proc/%s/smaps" % self.pid, "rt") as f: + first_line = f.readline() + current_block = [first_line] + + def get_blocks(): + data = {} + for line in f: + fields = line.split(None, 5) + if not fields[0].endswith(':'): + # new block section + yield (current_block.pop(), data) + current_block.append(line) + else: + try: + data[fields[0]] = int(fields[1]) * 1024 + except ValueError: + if fields[0].startswith('VmFlags:'): + # see issue #369 + continue + else: + raise ValueError("don't know how to inte" + "rpret line %r" % line) + yield (current_block.pop(), data) + + ls = [] + if first_line: # smaps file can be empty + for header, data in get_blocks(): + hfields = header.split(None, 5) + try: + addr, perms, offset, dev, inode, path = hfields + except ValueError: + addr, perms, offset, dev, inode, path = \ + hfields + [''] + if not path: + path = '[anon]' + else: + path = path.strip() + ls.append(( + addr, perms, path, + data['Rss:'], + data.get('Size:', 0), + data.get('Pss:', 0), + data.get('Shared_Clean:', 0), + data.get('Shared_Dirty:', 0), + data.get('Private_Clean:', 0), + data.get('Private_Dirty:', 0), + data.get('Referenced:', 0), + data.get('Anonymous:', 0), + data.get('Swap:', 0) + )) + return ls + + else: + def memory_maps(self): + msg = "couldn't find /proc/%s/smaps; kernel < 2.6.14 or " \ + "CONFIG_MMU kernel configuration option is not enabled" \ + % self.pid + raise NotImplementedError(msg) + + @wrap_exceptions_w_zombie + def cwd(self): + # readlink() might return paths containing null bytes causing + # problems when used with other fs-related functions (os.*, + # open(), ...) + path = os.readlink("/proc/%s/cwd" % self.pid) + return path.replace('\x00', '') + + @wrap_exceptions + def num_ctx_switches(self): + vol = unvol = None + with open("/proc/%s/status" % self.pid, "rb") as f: + for line in f: + if line.startswith(b"voluntary_ctxt_switches"): + vol = int(line.split()[1]) + elif line.startswith(b"nonvoluntary_ctxt_switches"): + unvol = int(line.split()[1]) + if vol is not None and unvol is not None: + return _common.pctxsw(vol, unvol) + raise NotImplementedError( + "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" + "fields were not found in /proc/%s/status; the kernel is " + "probably older than 2.6.23" % self.pid) + + @wrap_exceptions + def num_threads(self): + with open("/proc/%s/status" % self.pid, "rb") as f: + for line in f: + if line.startswith(b"Threads:"): + return int(line.split()[1]) + raise NotImplementedError("line not found") + + @wrap_exceptions + def threads(self): + thread_ids = os.listdir("/proc/%s/task" % self.pid) + thread_ids.sort() + retlist = [] + hit_enoent = False + for thread_id in thread_ids: + fname = "/proc/%s/task/%s/stat" % (self.pid, thread_id) + try: + with open(fname, 'rb') as f: + st = f.read().strip() + except IOError as err: + if err.errno == errno.ENOENT: + # no such file or directory; it means thread + # disappeared on us + hit_enoent = True + continue + raise + # ignore the first two values ("pid (exe)") + 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: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def nice_get(self): + # with open('/proc/%s/stat' % self.pid, 'r') as f: + # data = f.read() + # return int(data.split()[18]) + + # Use C implementation + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def cpu_affinity_get(self): + return cext.proc_cpu_affinity_get(self.pid) + + @wrap_exceptions + def cpu_affinity_set(self, cpus): + try: + cext.proc_cpu_affinity_set(self.pid, cpus) + except OSError as err: + if err.errno == errno.EINVAL: + 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 + + # only starting from kernel 2.6.13 + if hasattr(cext, "proc_ioprio_get"): + + @wrap_exceptions + def ionice_get(self): + ioclass, value = cext.proc_ioprio_get(self.pid) + if enum is not None: + ioclass = IOPriority(ioclass) + return _common.pionice(ioclass, value) + + @wrap_exceptions + def ionice_set(self, ioclass, value): + if value is not None: + if not PY3 and not isinstance(value, (int, long)): + msg = "value argument is not an integer (gor %r)" % value + raise TypeError(msg) + if not 0 <= value <= 8: + raise ValueError( + "value argument range expected is between 0 and 8") + + if ioclass in (IOPRIO_CLASS_NONE, None): + if value: + msg = "can't specify value with IOPRIO_CLASS_NONE " \ + "(got %r)" % value + raise ValueError(msg) + ioclass = IOPRIO_CLASS_NONE + value = 0 + elif ioclass == IOPRIO_CLASS_IDLE: + if value: + msg = "can't specify value with IOPRIO_CLASS_IDLE " \ + "(got %r)" % value + raise ValueError(msg) + value = 0 + elif ioclass in (IOPRIO_CLASS_RT, IOPRIO_CLASS_BE): + if value is None: + # TODO: add comment explaining why this is 4 (?) + value = 4 + else: + # otherwise we would get OSError(EVINAL) + raise ValueError("invalid ioclass argument %r" % ioclass) + + return cext.proc_ioprio_set(self.pid, ioclass, value) + + if HAS_PRLIMIT: + @wrap_exceptions + def rlimit(self, resource, limits=None): + # If pid is 0 prlimit() applies to the calling process and + # 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") + try: + if limits is None: + # get + return cext.linux_prlimit(self.pid, resource) + else: + # set + if len(limits) != 2: + raise ValueError( + "second argument must be a (soft, hard) tuple, " + "got %s" % repr(limits)) + soft, hard = limits + cext.linux_prlimit(self.pid, resource, soft, hard) + except OSError as err: + if err.errno == errno.ENOSYS and pid_exists(self.pid): + # I saw this happening on Travis: + # https://travis-ci.org/giampaolo/psutil/jobs/51368273 + raise ZombieProcess(self.pid, self._name, self._ppid) + else: + raise + + @wrap_exceptions + def status(self): + with open("/proc/%s/status" % self.pid, 'rb') as f: + for line in f: + if line.startswith(b"State:"): + letter = line.split()[1] + if PY3: + letter = letter.decode() + # XXX is '?' legit? (we're not supposed to return + # it anyway) + return PROC_STATUSES.get(letter, '?') + + @wrap_exceptions + def open_files(self): + retlist = [] + files = os.listdir("/proc/%s/fd" % self.pid) + hit_enoent = False + for fd in files: + file = "/proc/%s/fd/%s" % (self.pid, fd) + try: + file = os.readlink(file) + except OSError as err: + # ENOENT == file which is gone in the meantime + if err.errno in (errno.ENOENT, errno.ESRCH): + hit_enoent = True + continue + elif err.errno == errno.EINVAL: + # not a link + continue + else: + raise + else: + # If file is not an absolute path there's no way + # to tell whether it's a regular file or not, + # so we skip it. A regular file is always supposed + # to be absolutized though. + if file.startswith('/') and isfile_strict(file): + ntuple = _common.popenfile(file, int(fd)) + retlist.append(ntuple) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + ret = _connections.retrieve(kind, self.pid) + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return ret + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def ppid(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b"PPid:"): + # PPid: nnnn + return int(line.split()[1]) + raise NotImplementedError("line 'PPid' not found in %s" % fpath) + + @wrap_exceptions + def uids(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b'Uid:'): + _, real, effective, saved, fs = line.split() + return _common.puids(int(real), int(effective), int(saved)) + raise NotImplementedError("line 'Uid' not found in %s" % fpath) + + @wrap_exceptions + def gids(self): + fpath = "/proc/%s/status" % self.pid + with open(fpath, 'rb') as f: + for line in f: + if line.startswith(b'Gid:'): + _, real, effective, saved, fs = line.split() + return _common.pgids(int(real), int(effective), int(saved)) + raise NotImplementedError("line 'Gid' not found in %s" % fpath) diff --git a/python/psutil/psutil/_psosx.py b/python/psutil/psutil/_psosx.py new file mode 100644 index 0000000000..41875fe404 --- /dev/null +++ b/python/psutil/psutil/_psosx.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python + +# 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. + +"""OSX platform implementation.""" + +import errno +import functools +import os +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_osx as cext +from . import _psutil_posix as cext_posix +from ._common import conn_tmap, usage_percent, isfile_strict +from ._common import sockfam_to_enum, socktype_to_enum + + +__extra__all__ = [] + +# --- constants + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +AF_LINK = cext_posix.AF_LINK + +# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +PROC_STATUSES = { + cext.SIDL: _common.STATUS_IDLE, + cext.SRUN: _common.STATUS_RUNNING, + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SZOMB: _common.STATUS_ZOMBIE, +} + +scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) + +svmem = namedtuple( + 'svmem', ['total', 'available', 'percent', 'used', 'free', + 'active', 'inactive', 'wired']) + +pextmem = namedtuple('pextmem', ['rss', 'vms', 'pfaults', 'pageins']) + +pmmap_grouped = namedtuple( + 'pmmap_grouped', + 'path rss private swapped dirtied ref_count shadow_depth') + +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + + +# --- functions + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + total, active, inactive, wired, free = cext.virtual_mem() + avail = inactive + free + used = active + inactive + wired + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free, + active, inactive, wired) + + +def swap_memory(): + """Swap system memory as a (total, used, free, sin, sout) tuple.""" + total, used, free, sin, sout = cext.swap_mem() + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, sin, sout) + + +def cpu_times(): + """Return system CPU times as a namedtuple.""" + user, nice, system, idle = cext.cpu_times() + return scputimes(user, nice, system, idle) + + +def per_cpu_times(): + """Return system CPU times as a named tuple""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, nice, system, idle = cpu_t + item = scputimes(user, nice, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def disk_partitions(all=False): + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + if not os.path.isabs(device) or not os.path.exists(device): + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def users(): + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, tty, hostname, tstamp = item + if tty == '~': + continue # reboot or shutdown + if not tstamp: + continue + nt = _common.suser(user, tty or None, hostname or None, tstamp) + retlist.append(nt) + return retlist + + +def net_connections(kind='inet'): + # Note: on OSX this will fail with AccessDenied unless + # the process is owned by root. + ret = [] + for pid in pids(): + try: + cons = Process(pid).connections(kind) + except NoSuchProcess: + continue + else: + if cons: + for c in cons: + c = list(c) + [pid] + ret.append(_common.sconn(*c)) + return ret + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + names = net_io_counters().keys() + ret = {} + for name in names: + isup, duplex, speed, mtu = cext_posix.net_if_stats(name) + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +pids = cext.pids +pid_exists = _psposix.pid_exists +disk_usage = _psposix.disk_usage +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +net_if_addrs = cext_posix.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError exceptions into + NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + if err.errno == errno.ESRCH: + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + return cext.proc_exe(self.pid) + + @wrap_exceptions + def cmdline(self): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + return cext.proc_cmdline(self.pid) + + @wrap_exceptions + def ppid(self): + return cext.proc_ppid(self.pid) + + @wrap_exceptions + def cwd(self): + return cext.proc_cwd(self.pid) + + @wrap_exceptions + def uids(self): + real, effective, saved = cext.proc_uids(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + real, effective, saved = cext.proc_gids(self.pid) + return _common.pgids(real, effective, saved) + + @wrap_exceptions + def terminal(self): + tty_nr = cext.proc_tty_nr(self.pid) + tmap = _psposix._get_terminal_map() + try: + return tmap[tty_nr] + except KeyError: + return None + + @wrap_exceptions + def memory_info(self): + rss, vms = cext.proc_memory_info(self.pid)[:2] + return _common.pmem(rss, vms) + + @wrap_exceptions + def memory_info_ex(self): + rss, vms, pfaults, pageins = cext.proc_memory_info(self.pid) + return pextmem(rss, vms, pfaults * PAGESIZE, pageins * PAGESIZE) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def create_time(self): + return cext.proc_create_time(self.pid) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def num_threads(self): + return cext.proc_num_threads(self.pid) + + @wrap_exceptions + def open_files(self): + if self.pid == 0: + return [] + files = [] + rawlist = cext.proc_open_files(self.pid) + for path, fd in rawlist: + if isfile_strict(path): + ntuple = _common.popenfile(path, fd) + files.append(ntuple) + return files + + @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]))) + 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 + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.append(nt) + return ret + + @wrap_exceptions + def num_fds(self): + if self.pid == 0: + return 0 + return cext.proc_num_fds(self.pid) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) + + @wrap_exceptions + def nice_get(self): + return cext_posix.getpriority(self.pid) + + @wrap_exceptions + def nice_set(self, value): + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def status(self): + code = cext.proc_status(self.pid) + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def memory_maps(self): + return cext.proc_memory_maps(self.pid) diff --git a/python/psutil/psutil/_psposix.py b/python/psutil/psutil/_psposix.py new file mode 100644 index 0000000000..5bb16a386d --- /dev/null +++ b/python/psutil/psutil/_psposix.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +# 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. + +"""Routines common to all posix systems.""" + +import errno +import glob +import os +import sys +import time + +from ._common import sdiskusage, usage_percent, memoize +from ._compat import PY3, unicode + + +class TimeoutExpired(Exception): + pass + + +def pid_exists(pid): + """Check whether pid exists in the current process table.""" + if pid == 0: + # According to "man 2 kill" PID 0 has a special meaning: + # it refers to <<every process in the process group of the + # calling process>> so we don't want to go any further. + # If we get here it means this UNIX platform *does* have + # a process with id 0. + return True + try: + os.kill(pid, 0) + except OSError as err: + if err.errno == errno.ESRCH: + # ESRCH == No such process + return False + elif err.errno == errno.EPERM: + # EPERM clearly means there's a process to deny access to + return True + else: + # According to "man 2 kill" possible error values are + # (EINVAL, EPERM, ESRCH) therefore we should never get + # here. If we do let's be explicit in considering this + # an error. + raise err + else: + return True + + +def wait_pid(pid, timeout=None): + """Wait for process with pid 'pid' to terminate and return its + exit status code as an integer. + + If pid is not a children of os.getpid() (current process) just + waits until the process disappears and return None. + + If pid does not exist at all return None immediately. + + Raise TimeoutExpired on timeout expired. + """ + def check_timeout(delay): + if timeout is not None: + if timer() >= stop_at: + raise TimeoutExpired() + time.sleep(delay) + return min(delay * 2, 0.04) + + timer = getattr(time, 'monotonic', time.time) + if timeout is not None: + def waitcall(): + return os.waitpid(pid, os.WNOHANG) + stop_at = timer() + timeout + else: + def waitcall(): + return os.waitpid(pid, 0) + + delay = 0.0001 + while True: + try: + retpid, status = waitcall() + except OSError as err: + if err.errno == errno.EINTR: + delay = check_timeout(delay) + continue + elif err.errno == errno.ECHILD: + # This has two meanings: + # - pid is not a child of os.getpid() in which case + # we keep polling until it's gone + # - pid never existed in the first place + # In both cases we'll eventually return None as we + # can't determine its exit status code. + while True: + if pid_exists(pid): + delay = check_timeout(delay) + else: + return + else: + raise + else: + if retpid == 0: + # WNOHANG was used, pid is still running + delay = check_timeout(delay) + continue + # process exited due to a signal; return the integer of + # that signal + if os.WIFSIGNALED(status): + return os.WTERMSIG(status) + # process exited using exit(2) system call; return the + # integer exit(2) system call has been called with + elif os.WIFEXITED(status): + return os.WEXITSTATUS(status) + else: + # should never happen + raise RuntimeError("unknown process exit status") + + +def disk_usage(path): + """Return disk usage associated with path.""" + try: + st = os.statvfs(path) + except UnicodeEncodeError: + if not PY3 and isinstance(path, unicode): + # this is a bug with os.statvfs() and unicode on + # Python 2, see: + # - https://github.com/giampaolo/psutil/issues/416 + # - http://bugs.python.org/issue18695 + try: + path = path.encode(sys.getfilesystemencoding()) + except UnicodeEncodeError: + pass + st = os.statvfs(path) + else: + raise + free = (st.f_bavail * st.f_frsize) + total = (st.f_blocks * st.f_frsize) + used = (st.f_blocks - st.f_bfree) * st.f_frsize + percent = usage_percent(used, total, _round=1) + # NB: the percentage is -5% than what shown by df due to + # reserved blocks that we are currently not considering: + # http://goo.gl/sWGbH + return sdiskusage(total, used, free, percent) + + +@memoize +def _get_terminal_map(): + ret = {} + ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') + for name in ls: + assert name not in ret + try: + ret[os.stat(name).st_rdev] = name + except OSError as err: + if err.errno != errno.ENOENT: + raise + return ret diff --git a/python/psutil/psutil/_pssunos.py b/python/psutil/psutil/_pssunos.py new file mode 100644 index 0000000000..bc35a718c3 --- /dev/null +++ b/python/psutil/psutil/_pssunos.py @@ -0,0 +1,553 @@ +#!/usr/bin/env python + +# 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. + +"""Sun OS Solaris platform implementation.""" + +import errno +import os +import socket +import subprocess +import sys +from collections import namedtuple + +from . import _common +from . import _psposix +from . import _psutil_posix as cext_posix +from . import _psutil_sunos as cext +from ._common import isfile_strict, socktype_to_enum, sockfam_to_enum +from ._common import usage_percent +from ._compat import PY3 + + +__extra__all__ = ["CONN_IDLE", "CONN_BOUND"] + +PAGE_SIZE = os.sysconf('SC_PAGE_SIZE') +AF_LINK = cext_posix.AF_LINK + +CONN_IDLE = "IDLE" +CONN_BOUND = "BOUND" + +PROC_STATUSES = { + cext.SSLEEP: _common.STATUS_SLEEPING, + cext.SRUN: _common.STATUS_RUNNING, + cext.SZOMB: _common.STATUS_ZOMBIE, + cext.SSTOP: _common.STATUS_STOPPED, + cext.SIDL: _common.STATUS_IDLE, + cext.SONPROC: _common.STATUS_RUNNING, # same as run + cext.SWAIT: _common.STATUS_WAITING, +} + +TCP_STATUSES = { + cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED, + cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT, + cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV, + cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1, + cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2, + cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.TCPS_CLOSED: _common.CONN_CLOSE, + cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK, + cext.TCPS_LISTEN: _common.CONN_LISTEN, + cext.TCPS_CLOSING: _common.CONN_CLOSING, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, + cext.TCPS_IDLE: CONN_IDLE, # sunos specific + cext.TCPS_BOUND: CONN_BOUND, # sunos specific +} + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +pextmem = namedtuple('pextmem', ['rss', 'vms']) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss', 'anon', 'locked']) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + +# set later from __init__.py +NoSuchProcess = None +ZombieProcess = None +AccessDenied = None +TimeoutExpired = None + +# --- functions + +disk_io_counters = cext.disk_io_counters +net_io_counters = cext.net_io_counters +disk_usage = _psposix.disk_usage +net_if_addrs = cext_posix.net_if_addrs + + +def virtual_memory(): + # we could have done this with kstat, but imho this is good enough + total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE + # note: there's no difference on Solaris + free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE + used = total - free + percent = usage_percent(used, total, _round=1) + return svmem(total, avail, percent, used, free) + + +def swap_memory(): + sin, sout = cext.swap_mem() + # XXX + # we are supposed to get total/free by doing so: + # http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/ + # 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', '-k'], + stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + if PY3: + stdout = stdout.decode(sys.stdout.encoding) + if p.returncode != 0: + raise RuntimeError("'swap -l -k' failed (retcode=%s)" % p.returncode) + + lines = stdout.strip().split('\n')[1:] + if not lines: + raise RuntimeError('no swap device(s) configured') + total = free = 0 + for line in lines: + line = line.split() + t, f = line[-2:] + t = t.replace('K', '') + f = f.replace('K', '') + total += int(int(t) * 1024) + free += int(int(f) * 1024) + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sswap(total, used, free, percent, + sin * PAGE_SIZE, sout * PAGE_SIZE) + + +def pids(): + """Returns a list of PIDs currently running on the system.""" + return [int(x) for x in os.listdir('/proc') if x.isdigit()] + + +def pid_exists(pid): + """Check for the existence of a unix pid.""" + return _psposix.pid_exists(pid) + + +def cpu_times(): + """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""" + ret = cext.per_cpu_times() + return [scputimes(*x) for x in ret] + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + try: + return os.sysconf("SC_NPROCESSORS_ONLN") + except ValueError: + # mimic os.cpu_count() behavior + return None + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + localhost = (':0.0', ':0') + for item in rawlist: + user, tty, hostname, tstamp, user_process = 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 localhost: + hostname = 'localhost' + nt = _common.suser(user, tty, hostname, tstamp) + retlist.append(nt) + return retlist + + +def disk_partitions(all=False): + """Return system disk partitions.""" + # TODO - the filtering logic should be better checked so that + # it tries to reflect 'df' as much as possible + retlist = [] + partitions = cext.disk_partitions() + for partition in partitions: + device, mountpoint, fstype, opts = partition + if device == 'none': + device = '' + if not all: + # Differently from, say, Linux, we don't have a list of + # common fs types so the best we can do, AFAIK, is to + # filter by filesystem having a total size > 0. + if not disk_usage(mountpoint).total: + continue + ntuple = _common.sdiskpart(device, mountpoint, fstype, opts) + retlist.append(ntuple) + return retlist + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + connections (as opposed to connections opened by one process only). + Only INET sockets are returned (UNIX are not). + """ + cmap = _common.conn_tmap.copy() + 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]))) + families, types = _common.conn_tmap[kind] + rawlist = cext.net_connections(_pid, families, types) + ret = set() + for item in rawlist: + fd, fam, type_, laddr, raddr, status, pid = item + if fam not in families: + continue + if type_ not in types: + continue + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type_ = socktype_to_enum(type_) + if _pid == -1: + nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type_, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + """Get NIC stats (isup, duplex, speed, mtu).""" + ret = cext.net_if_stats() + for name, items in ret.items(): + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def wrap_exceptions(fun): + """Call callable into a try/except clause and translate ENOENT, + EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. + """ + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except EnvironmentError as err: + # support for private module import + if (NoSuchProcess is None or AccessDenied is None or + ZombieProcess is None): + raise + # ENOENT (no such file or directory) gets raised on open(). + # ESRCH (no such process) can get raised on read() if + # process is gone in meantime. + if err.errno in (errno.ENOENT, errno.ESRCH): + if not pid_exists(self.pid): + raise NoSuchProcess(self.pid, self._name) + else: + raise ZombieProcess(self.pid, self._name, self._ppid) + if err.errno in (errno.EPERM, errno.EACCES): + raise AccessDenied(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + # note: max len == 15 + return cext.proc_name_and_args(self.pid)[0] + + @wrap_exceptions + def exe(self): + # Will be guess 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. + self.cmdline() + return "" + + @wrap_exceptions + def cmdline(self): + return cext.proc_name_and_args(self.pid)[1].split(' ') + + @wrap_exceptions + def create_time(self): + return cext.proc_basic_info(self.pid)[3] + + @wrap_exceptions + def num_threads(self): + return cext.proc_basic_info(self.pid)[5] + + @wrap_exceptions + def nice_get(self): + # For some reason getpriority(3) return ESRCH (no such process) + # for certain low-pid processes, no matter what (even as root). + # The process actually exists though, as it has a name, + # creation time, etc. + # The best thing we can do here appears to be raising AD. + # Note: tested on Solaris 11; on Open Solaris 5 everything is + # fine. + try: + return cext_posix.getpriority(self.pid) + except EnvironmentError as err: + # 48 is 'operation not supported' but errno does not expose + # it. It occurs for low system pids. + if err.errno in (errno.ENOENT, errno.ESRCH, 48): + if pid_exists(self.pid): + raise AccessDenied(self.pid, self._name) + raise + + @wrap_exceptions + def nice_set(self, value): + if self.pid in (2, 3): + # Special case PIDs: internally setpriority(3) return ESRCH + # (no such process), no matter what. + # The process actually exists though, as it has a name, + # creation time, etc. + raise AccessDenied(self.pid, self._name) + return cext_posix.setpriority(self.pid, value) + + @wrap_exceptions + def ppid(self): + return cext.proc_basic_info(self.pid)[0] + + @wrap_exceptions + def uids(self): + real, effective, saved, _, _, _ = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def gids(self): + _, _, _, real, effective, saved = cext.proc_cred(self.pid) + return _common.puids(real, effective, saved) + + @wrap_exceptions + def cpu_times(self): + user, system = cext.proc_cpu_times(self.pid) + return _common.pcputimes(user, system) + + @wrap_exceptions + def terminal(self): + hit_enoent = False + tty = wrap_exceptions( + cext.proc_basic_info(self.pid)[0]) + if tty != cext.PRNODEV: + for x in (0, 1, 2, 255): + try: + return os.readlink('/proc/%d/path/%d' % (self.pid, x)) + except OSError as err: + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + + @wrap_exceptions + def cwd(self): + # /proc/PID/path/cwd may not be resolved by readlink() even if + # it exists (ls shows it). If that's the case and the process + # is still alive return None (we can return None also on BSD). + # Reference: http://goo.gl/55XgO + try: + return os.readlink("/proc/%s/path/cwd" % self.pid) + except OSError as err: + if err.errno == errno.ENOENT: + os.stat("/proc/%s" % self.pid) + return None + raise + + @wrap_exceptions + def memory_info(self): + ret = cext.proc_basic_info(self.pid) + rss, vms = ret[1] * 1024, ret[2] * 1024 + return _common.pmem(rss, vms) + + # it seems Solaris uses rss and vms only + memory_info_ex = memory_info + + @wrap_exceptions + def status(self): + code = cext.proc_basic_info(self.pid)[6] + # XXX is '?' legit? (we're not supposed to return it anyway) + return PROC_STATUSES.get(code, '?') + + @wrap_exceptions + def threads(self): + ret = [] + tids = os.listdir('/proc/%d/lwp' % self.pid) + hit_enoent = False + for tid in tids: + tid = int(tid) + try: + utime, stime = cext.query_process_thread( + self.pid, tid) + except EnvironmentError as err: + # ENOENT == thread gone in meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + nt = _common.pthread(tid, utime, stime) + ret.append(nt) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return ret + + @wrap_exceptions + def open_files(self): + retlist = [] + hit_enoent = False + pathdir = '/proc/%d/path' % self.pid + for fd in os.listdir('/proc/%d/fd' % self.pid): + path = os.path.join(pathdir, fd) + if os.path.islink(path): + try: + file = os.readlink(path) + except OSError as err: + # ENOENT == file which is gone in the meantime + if err.errno == errno.ENOENT: + hit_enoent = True + continue + raise + else: + if isfile_strict(file): + retlist.append(_common.popenfile(file, int(fd))) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + def _get_unix_sockets(self, pid): + """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) + stdout, stderr = p.communicate() + if PY3: + 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) + if 'no such process' in stderr.lower(): + raise NoSuchProcess(self.pid, self._name) + raise RuntimeError("%r command error\n%s" % (cmd, stderr)) + + lines = stdout.split('\n')[2:] + for i, line in enumerate(lines): + line = line.lstrip() + if line.startswith('sockname: AF_UNIX'): + path = line.split(' ', 2)[2] + type = lines[i - 2].strip() + if type == 'SOCK_STREAM': + type = socket.SOCK_STREAM + elif type == 'SOCK_DGRAM': + type = socket.SOCK_DGRAM + else: + type = -1 + yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE) + + @wrap_exceptions + def connections(self, kind='inet'): + ret = net_connections(kind, _pid=self.pid) + # The underlying C implementation retrieves all OS connections + # and filters them by PID. At this point we can't tell whether + # an empty list means there were no connections for process or + # process is no longer active so we force NSP in case the PID + # is no longer there. + if not ret: + os.stat('/proc/%s' % self.pid) # will raise NSP if process is gone + + # UNIX sockets + if kind in ('all', 'unix'): + 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') + nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked') + + @wrap_exceptions + def memory_maps(self): + def toaddr(start, end): + return '%s-%s' % (hex(start)[2:].strip('L'), + hex(end)[2:].strip('L')) + + retlist = [] + rawlist = cext.proc_memory_maps(self.pid) + hit_enoent = False + for item in rawlist: + addr, addrsize, perm, name, rss, anon, locked = item + addr = toaddr(addr, addrsize) + if not name.startswith('['): + try: + name = os.readlink('/proc/%s/path/%s' % (self.pid, name)) + except OSError as err: + if err.errno == errno.ENOENT: + # sometimes the link may not be resolved by + # readlink() even if it exists (ls shows it). + # If that's the case we just return the + # unresolved link path. + # This seems an incosistency with /proc similar + # to: http://goo.gl/55XgO + name = '/proc/%s/path/%s' % (self.pid, name) + hit_enoent = True + else: + raise + retlist.append((addr, perm, name, rss, anon, locked)) + if hit_enoent: + # raise NSP if the process disappeared on us + os.stat('/proc/%s' % self.pid) + return retlist + + @wrap_exceptions + def num_fds(self): + return len(os.listdir("/proc/%s/fd" % self.pid)) + + @wrap_exceptions + def num_ctx_switches(self): + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) + + @wrap_exceptions + def wait(self, timeout=None): + try: + return _psposix.wait_pid(self.pid, timeout) + except _psposix.TimeoutExpired: + # support for private module import + if TimeoutExpired is None: + raise + raise TimeoutExpired(timeout, self.pid, self._name) diff --git a/python/psutil/psutil/_psutil_bsd.c b/python/psutil/psutil/_psutil_bsd.c new file mode 100644 index 0000000000..7b6e56173d --- /dev/null +++ b/python/psutil/psutil/_psutil_bsd.c @@ -0,0 +1,2296 @@ +/* + * 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. + * + * FreeBSD platform-specific module methods for _psutil_bsd + */ + + +#include <Python.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <fcntl.h> +#include <paths.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/param.h> +#include <sys/user.h> +#include <sys/proc.h> +#include <sys/file.h> +#include <sys/cpuset.h> +#include <net/route.h> + +#include <sys/socket.h> +#include <sys/socketvar.h> // for struct xsocket +#include <sys/un.h> +#include <sys/unpcb.h> +#include <sys/sockio.h> +// for xinpcb struct +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> +#include <netinet/tcp_var.h> // for struct xtcpcb +#include <netinet/tcp_fsm.h> // for TCP connection states +#include <arpa/inet.h> // for inet_ntop() + +#if __FreeBSD_version < 900000 +#include <utmp.h> // system users +#else +#include <utmpx.h> +#endif +#include <devstat.h> // get io counters +#include <sys/vmmeter.h> // needed for vmtotal struct +#include <libutil.h> // process open files, shared libs (kinfo_getvmmap) +#include <sys/mount.h> + +#include <net/if.h> // net io counters +#include <net/if_dl.h> +#include <net/route.h> +#include <net/if_media.h> + +#include <netinet/in.h> // process open files/connections +#include <sys/un.h> + +#include "_psutil_bsd.h" +#include "_psutil_common.h" +#include "arch/bsd/process_info.h" + + +// convert a timeval struct to a double +#define TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + + +/* + * Utility function which fills a kinfo_proc struct based on process pid + */ +static int +psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc) +{ + int mib[4]; + size_t size; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + + size = sizeof(struct kinfo_proc); + + if (sysctl((int *)mib, 4, proc, &size, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + + // sysctl stores 0 in the size if we can't find the process information. + if (size == 0) { + NoSuchProcess(); + return -1; + } + return 0; +} + + +/* + * Set exception to AccessDenied if pid exists else NoSuchProcess. + */ +void +psutil_raise_ad_or_nsp(long pid) { + if (psutil_pid_exists(pid) == 0) + NoSuchProcess(); + else + AccessDenied(); +} + + +/* + * 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 *retlist = PyList_New(0); + PyObject *pid = NULL; + + if (retlist == NULL) + return NULL; + if (psutil_get_proc_list(&proclist, &num_processes) != 0) { + PyErr_SetString(PyExc_RuntimeError, + "failed to retrieve process list."); + goto error; + } + + if (num_processes > 0) { + orig_address = proclist; // save so we can free it after we're done + for (idx = 0; idx < num_processes; idx++) { + pid = Py_BuildValue("i", proclist->ki_pid); + if (!pid) + goto error; + if (PyList_Append(retlist, pid)) + goto error; + Py_DECREF(pid); + proclist++; + } + free(orig_address); + } + + return retlist; + +error: + Py_XDECREF(pid); + Py_DECREF(retlist); + if (orig_address != NULL) + free(orig_address); + return NULL; +} + + +/* + * 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 boottime; + size_t len = sizeof(boottime); + + if (sysctl(request, 2, &boottime, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return Py_BuildValue("d", (double)boottime.tv_sec); +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("s", kp.ki_comm); +} + + +/* + * Return process pathname executable. + * Thanks to Robert N. M. Watson: + * http://fxr.googlebit.com/source/usr.bin/procstat/procstat_bin.c?v=8-CURRENT + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) +{ + long pid; + char pathname[PATH_MAX]; + int error; + int mib[4]; + size_t size; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = pid; + + size = sizeof(pathname); + error = sysctl(mib, 4, pathname, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (size == 0 || strlen(pathname) == 0) { + if (psutil_pid_exists(pid) == 0) + return NoSuchProcess(); + else + strcpy(pathname, ""); + } + return Py_BuildValue("s", pathname); +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) +{ + long pid; + PyObject *arglist = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + // get the commandline, defined in arch/bsd/process_info.c + arglist = psutil_get_arg_list(pid); + + // psutil_get_arg_list() returns NULL only if psutil_cmd_args + // failed with ESRCH (no process with that PID) + if (NULL == arglist) + return PyErr_SetFromErrno(PyExc_OSError); + return Py_BuildValue("N", arglist); +} + + +/* + * Return process parent pid from kinfo_proc as a Python integer. + */ +static PyObject * +psutil_proc_ppid(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("l", (long)kp.ki_ppid); +} + + +/* + * Return process status as a Python integer. + */ +static PyObject * +psutil_proc_status(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("i", (int)kp.ki_stat); +} + + +/* + * Return process real, effective and saved user ids from kinfo_proc + * as a Python tuple. + */ +static PyObject * +psutil_proc_uids(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("lll", + (long)kp.ki_ruid, + (long)kp.ki_uid, + (long)kp.ki_svuid); +} + + +/* + * Return process real, effective and saved group ids from kinfo_proc + * as a Python tuple. + */ +static PyObject * +psutil_proc_gids(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("lll", + (long)kp.ki_rgid, + (long)kp.ki_groups[0], + (long)kp.ki_svuid); +} + + +/* + * Return process real, effective and saved group ids from kinfo_proc + * as a Python tuple. + */ +static PyObject * +psutil_proc_tty_nr(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("i", kp.ki_tdev); +} + + +/* + * Return the number of context switches performed by process as a tuple. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("(ll)", + kp.ki_rusage.ru_nvcsw, + kp.ki_rusage.ru_nivcsw); +} + + +/* + * Return number of threads used by process as a Python integer. + */ +static PyObject * +psutil_proc_num_threads(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("l", (long)kp.ki_numthreads); +} + + +/* + * Retrieves all threads used by process returning a list of tuples + * including thread id, user time and system time. + * Thanks to Robert N. M. Watson: + * http://fxr.googlebit.com/source/usr.bin/procstat/ + * procstat_threads.c?v=8-CURRENT + */ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) +{ + long pid; + int mib[4]; + struct kinfo_proc *kip = NULL; + struct kinfo_proc *kipp = NULL; + int error; + unsigned int i; + size_t size; + PyObject *retList = PyList_New(0); + PyObject *pyTuple = NULL; + + if (retList == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + // we need to re-query for thread information, so don't use *kipp + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID | KERN_PROC_INC_THREAD; + mib[3] = pid; + + size = 0; + error = sysctl(mib, 4, NULL, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (size == 0) { + NoSuchProcess(); + goto error; + } + + kip = malloc(size); + if (kip == NULL) { + PyErr_NoMemory(); + goto error; + } + + error = sysctl(mib, 4, kip, &size, NULL, 0); + if (error == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + if (size == 0) { + NoSuchProcess(); + goto error; + } + + for (i = 0; i < size / sizeof(*kipp); i++) { + kipp = &kip[i]; + pyTuple = Py_BuildValue("Idd", + kipp->ki_tid, + TV2DOUBLE(kipp->ki_rusage.ru_utime), + TV2DOUBLE(kipp->ki_rusage.ru_stime)); + if (pyTuple == NULL) + goto error; + if (PyList_Append(retList, pyTuple)) + goto error; + Py_DECREF(pyTuple); + } + free(kip); + return retList; + +error: + Py_XDECREF(pyTuple); + Py_DECREF(retList); + if (kip != NULL) + free(kip); + return NULL; +} + + +/* + * Return a Python tuple (user_time, kernel_time) + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) +{ + long pid; + double user_t, sys_t; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + // convert from microseconds to seconds + user_t = TV2DOUBLE(kp.ki_rusage.ru_utime); + sys_t = TV2DOUBLE(kp.ki_rusage.ru_stime); + return Py_BuildValue("(dd)", user_t, sys_t); +} + + +/* + * Return the number of logical CPUs in the system. + * XXX this could be shared with OSX + */ +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); +} + + +/* + * Return an XML string from which we'll determine the number of + * physical CPU cores in the system. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) +{ + void *topology = NULL; + size_t size = 0; + PyObject *py_str; + + if (sysctlbyname("kern.sched.topology_spec", NULL, &size, NULL, 0)) + goto error; + + topology = malloc(size); + if (!topology) { + PyErr_NoMemory(); + return NULL; + } + + if (sysctlbyname("kern.sched.topology_spec", topology, &size, NULL, 0)) + goto error; + + py_str = Py_BuildValue("s", topology); + free(topology); + return py_str; + +error: + if (topology != NULL) + free(topology); + Py_RETURN_NONE; +} + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_create_time(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("d", TV2DOUBLE(kp.ki_start)); +} + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_io_counters(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + // there's apparently no way to determine bytes count, hence return -1. + return Py_BuildValue("(llll)", + kp.ki_rusage.ru_inblock, + kp.ki_rusage.ru_oublock, + -1, + -1); +} + + +/* + * Return extended memory info for a process as a Python tuple. + */ +static PyObject * +psutil_proc_memory_info(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("(lllll)", + ptoa(kp.ki_rssize), // rss + (long)kp.ki_size, // vms + ptoa(kp.ki_tsize), // text + ptoa(kp.ki_dsize), // data + ptoa(kp.ki_ssize)); // stack +} + + +/* + * Return virtual memory usage statistics. + */ +static PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) +{ + unsigned int total, active, inactive, wired, cached, free; + size_t size = sizeof(total); + struct vmtotal vm; + int mib[] = {CTL_VM, VM_METER}; + long pagesize = getpagesize(); +#if __FreeBSD_version > 702101 + long buffers; +#else + int buffers; +#endif + size_t buffers_size = sizeof(buffers); + + if (sysctlbyname("vm.stats.vm.v_page_count", &total, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_active_count", &active, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_inactive_count", + &inactive, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_wire_count", &wired, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_cache_count", &cached, &size, NULL, 0)) + goto error; + if (sysctlbyname("vm.stats.vm.v_free_count", &free, &size, NULL, 0)) + goto error; + if (sysctlbyname("vfs.bufspace", &buffers, &buffers_size, NULL, 0)) + goto error; + + size = sizeof(vm); + if (sysctl(mib, 2, &vm, &size, NULL, 0) != 0) + goto error; + + return Py_BuildValue("KKKKKKKK", + (unsigned long long) total * pagesize, + (unsigned long long) free * pagesize, + (unsigned long long) active * pagesize, + (unsigned long long) inactive * pagesize, + (unsigned long long) wired * pagesize, + (unsigned long long) cached * pagesize, + (unsigned long long) buffers, + (unsigned long long) (vm.t_vmshr + vm.t_rmshr) * pagesize // shared + ); + +error: + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +#ifndef _PATH_DEVNULL +#define _PATH_DEVNULL "/dev/null" +#endif + +/* + * Return swap memory stats (see 'swapinfo' cmdline tool) + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) +{ + kvm_t *kd; + struct kvm_swap kvmsw[1]; + unsigned int swapin, swapout, nodein, nodeout; + size_t size = sizeof(unsigned int); + + kd = kvm_open(NULL, _PATH_DEVNULL, NULL, O_RDONLY, "kvm_open failed"); + if (kd == NULL) { + PyErr_SetString(PyExc_RuntimeError, "kvm_open failed"); + return NULL; + } + + if (kvm_getswapinfo(kd, kvmsw, 1, 0) < 0) { + kvm_close(kd); + PyErr_SetString(PyExc_RuntimeError, "kvm_getswapinfo failed"); + return NULL; + } + + kvm_close(kd); + + if (sysctlbyname("vm.stats.vm.v_swapin", &swapin, &size, NULL, 0) == -1) + goto sbn_error; + if (sysctlbyname("vm.stats.vm.v_swapout", &swapout, &size, NULL, 0) == -1) + goto sbn_error; + if (sysctlbyname("vm.stats.vm.v_vnodein", &nodein, &size, NULL, 0) == -1) + goto sbn_error; + if (sysctlbyname("vm.stats.vm.v_vnodeout", &nodeout, &size, NULL, 0) == -1) + goto sbn_error; + + return Py_BuildValue("(iiiII)", + kvmsw[0].ksw_total, // total + kvmsw[0].ksw_used, // used + kvmsw[0].ksw_total - kvmsw[0].ksw_used, // free + swapin + swapout, // swap in + nodein + nodeout); // swap out + +sbn_error: + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* + * Return a Python tuple representing user, kernel and idle CPU times + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) +{ + long cpu_time[CPUSTATES]; + size_t size; + + size = sizeof(cpu_time); + + if (sysctlbyname("kern.cp_time", &cpu_time, &size, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return Py_BuildValue("(ddddd)", + (double)cpu_time[CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC + ); +} + + +/* + * XXX + * These functions are available on FreeBSD 8 only. + * In the upper python layer we do various tricks to avoid crashing + * and/or to provide alternatives where possible. + */ + + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +/* + * Return files opened by process as a list of (path, fd) tuples. + * TODO: this is broken as it may report empty paths. 'procstat' + * utility has the same problem see: + * https://github.com/giampaolo/psutil/issues/595 + */ +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) +{ + long pid; + int i, cnt; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + struct kinfo_proc kipp; + PyObject *retList = PyList_New(0); + PyObject *tuple = NULL; + + if (retList == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kipp) == -1) + goto error; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + for (i = 0; i < cnt; i++) { + kif = &freep[i]; + if ((kif->kf_type == KF_TYPE_VNODE) && + (kif->kf_vnode_type == KF_VTYPE_VREG)) + { + tuple = Py_BuildValue("(si)", kif->kf_path, kif->kf_fd); + if (tuple == NULL) + goto error; + if (PyList_Append(retList, tuple)) + goto error; + Py_DECREF(tuple); + } + } + free(freep); + return retList; + +error: + Py_XDECREF(tuple); + Py_DECREF(retList); + if (freep != NULL) + free(freep); + return NULL; +} + + +/* + * Return files opened by process as a list of (path, fd) tuples + */ +static PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) +{ + long pid; + int cnt; + + struct kinfo_file *freep; + struct kinfo_proc kipp; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_kinfo_proc(pid, &kipp) == -1) + return NULL; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + return NULL; + } + free(freep); + + return Py_BuildValue("i", cnt); +} + + +/* + * Return process current working directory. + */ +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) +{ + long pid; + PyObject *path = NULL; + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + struct kinfo_proc kipp; + + int i, cnt; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kipp) == -1) + goto error; + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + for (i = 0; i < cnt; i++) { + kif = &freep[i]; + if (kif->kf_fd == KF_FD_TYPE_CWD) { + path = Py_BuildValue("s", kif->kf_path); + if (!path) + goto error; + break; + } + } + /* + * For lower pids it seems we can't retrieve any information + * (lsof can't do that it either). Since this happens even + * as root we return an empty string instead of AccessDenied. + */ + if (path == NULL) + path = Py_BuildValue("s", ""); + free(freep); + return path; + +error: + Py_XDECREF(path); + if (freep != NULL) + free(freep); + return NULL; +} + + +// The tcplist fetching and walking is borrowed from netstat/inet.c. +static char * +psutil_fetch_tcplist(void) +{ + char *buf; + size_t len; + + for (;;) { + if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) { + free(buf); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + return buf; + } +} + +static int +psutil_sockaddr_port(int family, struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (sin->sin_port); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (sin6->sin6_port); + } +} + +static void * +psutil_sockaddr_addr(int family, struct sockaddr_storage *ss) +{ + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + + if (family == AF_INET) { + sin = (struct sockaddr_in *)ss; + return (&sin->sin_addr); + } + else { + sin6 = (struct sockaddr_in6 *)ss; + return (&sin6->sin6_addr); + } +} + +static socklen_t +psutil_sockaddr_addrlen(int family) +{ + if (family == AF_INET) + return (sizeof(struct in_addr)); + else + return (sizeof(struct in6_addr)); +} + +static int +psutil_sockaddr_matches(int family, int port, void *pcb_addr, + struct sockaddr_storage *ss) +{ + if (psutil_sockaddr_port(family, ss) != port) + return (0); + return (memcmp(psutil_sockaddr_addr(family, ss), pcb_addr, + psutil_sockaddr_addrlen(family)) == 0); +} + +static struct tcpcb * +psutil_search_tcplist(char *buf, struct kinfo_file *kif) +{ + struct tcpcb *tp; + struct inpcb *inp; + struct xinpgen *xig, *oxig; + struct xsocket *so; + + oxig = xig = (struct xinpgen *)buf; + for (xig = (struct xinpgen *)((char *)xig + xig->xig_len); + xig->xig_len > sizeof(struct xinpgen); + xig = (struct xinpgen *)((char *)xig + xig->xig_len)) { + tp = &((struct xtcpcb *)xig)->xt_tp; + inp = &((struct xtcpcb *)xig)->xt_inp; + so = &((struct xtcpcb *)xig)->xt_socket; + + if (so->so_type != kif->kf_sock_type || + so->xso_family != kif->kf_sock_domain || + so->xso_protocol != kif->kf_sock_protocol) + continue; + + if (kif->kf_sock_domain == AF_INET) { + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_lport, &inp->inp_laddr, + &kif->kf_sa_local)) + continue; + if (!psutil_sockaddr_matches( + AF_INET, inp->inp_fport, &inp->inp_faddr, + &kif->kf_sa_peer)) + continue; + } else { + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_lport, &inp->in6p_laddr, + &kif->kf_sa_local)) + continue; + if (!psutil_sockaddr_matches( + AF_INET6, inp->inp_fport, &inp->in6p_faddr, + &kif->kf_sa_peer)) + continue; + } + + return (tp); + } + return NULL; +} + + +// a signaler for connections without an actual status +static int PSUTIL_CONN_NONE = 128; + +/* + * Return connections opened by process. + */ +static PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) +{ + long pid; + int i, cnt; + + struct kinfo_file *freep = NULL; + struct kinfo_file *kif; + char *tcplist = NULL; + struct tcpcb *tcp; + + PyObject *retList = PyList_New(0); + PyObject *tuple = NULL; + PyObject *laddr = NULL; + PyObject *raddr = NULL; + PyObject *af_filter = NULL; + PyObject *type_filter = NULL; + PyObject *_family = NULL; + PyObject *_type = NULL; + + if (retList == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "lOO", &pid, &af_filter, &type_filter)) + goto error; + if (!PySequence_Check(af_filter) || !PySequence_Check(type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + freep = kinfo_getfile(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + + tcplist = psutil_fetch_tcplist(); + if (tcplist == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < cnt; i++) { + int lport, rport, state; + char lip[200], rip[200]; + char path[PATH_MAX]; + int inseq; + tuple = NULL; + laddr = NULL; + raddr = NULL; + + kif = &freep[i]; + if (kif->kf_type == KF_TYPE_SOCKET) { + // apply filters + _family = PyLong_FromLong((long)kif->kf_sock_domain); + inseq = PySequence_Contains(af_filter, _family); + Py_DECREF(_family); + if (inseq == 0) + continue; + _type = PyLong_FromLong((long)kif->kf_sock_type); + inseq = PySequence_Contains(type_filter, _type); + Py_DECREF(_type); + if (inseq == 0) + continue; + // IPv4 / IPv6 socket + if ((kif->kf_sock_domain == AF_INET) || + (kif->kf_sock_domain == AF_INET6)) { + // fill status + state = PSUTIL_CONN_NONE; + if (kif->kf_sock_type == SOCK_STREAM) { + tcp = psutil_search_tcplist(tcplist, kif); + if (tcp != NULL) + state = (int)tcp->t_state; + } + + // build addr and port + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, + &kif->kf_sa_local), + lip, + sizeof(lip)); + inet_ntop( + kif->kf_sock_domain, + psutil_sockaddr_addr(kif->kf_sock_domain, + &kif->kf_sa_peer), + rip, + sizeof(rip)); + lport = htons(psutil_sockaddr_port(kif->kf_sock_domain, + &kif->kf_sa_local)); + rport = htons(psutil_sockaddr_port(kif->kf_sock_domain, + &kif->kf_sa_peer)); + + // construct python tuple/list + laddr = Py_BuildValue("(si)", lip, lport); + if (!laddr) + goto error; + if (rport != 0) + raddr = Py_BuildValue("(si)", rip, rport); + else + raddr = Py_BuildValue("()"); + if (!raddr) + goto error; + tuple = Py_BuildValue("(iiiNNi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + laddr, + raddr, + state); + if (!tuple) + goto error; + if (PyList_Append(retList, tuple)) + goto error; + Py_DECREF(tuple); + } + // UNIX socket + else if (kif->kf_sock_domain == AF_UNIX) { + struct sockaddr_un *sun; + + sun = (struct sockaddr_un *)&kif->kf_sa_local; + snprintf( + path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + + tuple = Py_BuildValue("(iiisOi)", + kif->kf_fd, + kif->kf_sock_domain, + kif->kf_sock_type, + path, + Py_None, + PSUTIL_CONN_NONE); + if (!tuple) + goto error; + if (PyList_Append(retList, tuple)) + goto error; + Py_DECREF(tuple); + Py_INCREF(Py_None); + } + } + } + free(freep); + free(tcplist); + return retList; + +error: + Py_XDECREF(tuple); + Py_XDECREF(laddr); + Py_XDECREF(raddr); + Py_DECREF(retList); + if (freep != NULL) + free(freep); + if (tcplist != NULL) + free(tcplist); + return NULL; +} + + +/* + * Return a Python list of tuple representing per-cpu times + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) +{ + static int maxcpus; + int mib[2]; + int ncpu; + size_t len; + size_t size; + int i; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + // retrieve maxcpus value + size = sizeof(maxcpus); + if (sysctlbyname("kern.smp.maxcpus", &maxcpus, &size, NULL, 0) < 0) { + Py_DECREF(py_retlist); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + long cpu_time[maxcpus][CPUSTATES]; + + // retrieve the number of cpus + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(ncpu); + if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // per-cpu info + size = sizeof(cpu_time); + if (sysctlbyname("kern.cp_times", &cpu_time, &size, NULL, 0) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < ncpu; i++) { + py_cputime = Py_BuildValue( + "(ddddd)", + (double)cpu_time[i][CP_USER] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_NICE] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_SYS] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_IDLE] / CLOCKS_PER_SEC, + (double)cpu_time[i][CP_INTR] / CLOCKS_PER_SEC); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + } + + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + return NULL; +} + + +// remove spaces from string +void remove_spaces(char *str) { + char *p1 = str; + char *p2 = str; + do + while (*p2 == ' ') + p2++; + while ((*p1++ = *p2++)); +} + + +/* + * Return a list of tuples for every process memory maps. + * 'procstat' cmdline utility has been used as an example. + */ +static PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) +{ + long pid; + int ptrwidth; + int i, cnt; + char addr[1000]; + char perms[4]; + const char *path; + struct kinfo_proc kp; + struct kinfo_vmentry *freep = NULL; + struct kinfo_vmentry *kve; + ptrwidth = 2 * sizeof(void *); + PyObject *pytuple = NULL; + PyObject *retlist = PyList_New(0); + + if (retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (psutil_kinfo_proc(pid, &kp) == -1) + goto error; + + freep = kinfo_getvmmap(pid, &cnt); + if (freep == NULL) { + psutil_raise_ad_or_nsp(pid); + goto error; + } + for (i = 0; i < cnt; i++) { + pytuple = NULL; + kve = &freep[i]; + addr[0] = '\0'; + perms[0] = '\0'; + sprintf(addr, "%#*jx-%#*jx", ptrwidth, (uintmax_t)kve->kve_start, + ptrwidth, (uintmax_t)kve->kve_end); + remove_spaces(addr); + strlcat(perms, kve->kve_protection & KVME_PROT_READ ? "r" : "-", + sizeof(perms)); + strlcat(perms, kve->kve_protection & KVME_PROT_WRITE ? "w" : "-", + sizeof(perms)); + strlcat(perms, kve->kve_protection & KVME_PROT_EXEC ? "x" : "-", + sizeof(perms)); + + if (strlen(kve->kve_path) == 0) { + switch (kve->kve_type) { + case KVME_TYPE_NONE: + path = "[none]"; + break; + case KVME_TYPE_DEFAULT: + path = "[default]"; + break; + case KVME_TYPE_VNODE: + path = "[vnode]"; + break; + case KVME_TYPE_SWAP: + path = "[swap]"; + break; + case KVME_TYPE_DEVICE: + path = "[device]"; + break; + case KVME_TYPE_PHYS: + path = "[phys]"; + break; + case KVME_TYPE_DEAD: + path = "[dead]"; + break; + case KVME_TYPE_SG: + path = "[sg]"; + break; + case KVME_TYPE_UNKNOWN: + path = "[unknown]"; + break; + default: + path = "[?]"; + break; + } + } + else { + path = kve->kve_path; + } + + pytuple = Py_BuildValue("sssiiii", + addr, // "start-end" address + perms, // "rwx" permissions + path, // path + kve->kve_resident, // rss + kve->kve_private_resident, // private + kve->kve_ref_count, // ref count + kve->kve_shadow_count); // shadow count + if (!pytuple) + goto error; + if (PyList_Append(retlist, pytuple)) + goto error; + Py_DECREF(pytuple); + } + free(freep); + return retlist; + +error: + Py_XDECREF(pytuple); + Py_DECREF(retlist); + if (freep != NULL) + free(freep); + return NULL; +} +#endif + + +/* + * 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; + long len; + uint64_t flags; + char opts[200]; + struct statfs *fs = NULL; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + 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++) { + py_tuple = NULL; + 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_SUIDDIR) + strlcat(opts, ",suiddir", sizeof(opts)); + if (flags & MNT_SOFTDEP) + strlcat(opts, ",softdep", sizeof(opts)); + if (flags & MNT_NOSYMFOLLOW) + strlcat(opts, ",nosymfollow", sizeof(opts)); + if (flags & MNT_GJOURNAL) + strlcat(opts, ",gjournal", sizeof(opts)); + if (flags & MNT_MULTILABEL) + strlcat(opts, ",multilabel", sizeof(opts)); + if (flags & MNT_ACLS) + strlcat(opts, ",acls", sizeof(opts)); + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); + if (flags & MNT_NOCLUSTERR) + strlcat(opts, ",noclusterr", sizeof(opts)); + if (flags & MNT_NOCLUSTERW) + strlcat(opts, ",noclusterw", sizeof(opts)); + if (flags & MNT_NFS4ACLS) + strlcat(opts, ",nfs4acls", sizeof(opts)); + + py_tuple = Py_BuildValue("(ssss)", + fs[i].f_mntfromname, // device + fs[i].f_mntonname, // 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_DECREF(py_tuple); + } + + free(fs); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (fs != NULL) + free(fs); + return NULL; +} + + +/* + * 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]; + size_t len; + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + + if (py_retdict == NULL) + return NULL; + 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_IFLIST; // operation + mib[5] = 0; + + 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; ) { + py_ifc_info = NULL; + ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + + if (ifm->ifm_type == RTM_IFINFO) { + struct if_msghdr *if2m = (struct if_msghdr *)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; + // XXX: ignore usbus interfaces: + // http://lists.freebsd.org/pipermail/freebsd-current/ + // 2011-October/028752.html + // 'ifconfig -a' doesn't show them, nor do we. + if (strncmp(ifc_name, "usbus", 5) == 0) + continue; + + 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_DECREF(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) +{ + int i; + struct statinfo stats; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + + if (py_retdict == NULL) + return NULL; + if (devstat_checkversion(NULL) < 0) { + PyErr_Format(PyExc_RuntimeError, "devstat_checkversion() failed"); + goto error; + } + + stats.dinfo = (struct devinfo *)malloc(sizeof(struct devinfo)); + if (stats.dinfo == NULL) { + PyErr_NoMemory(); + goto error; + } + bzero(stats.dinfo, sizeof(struct devinfo)); + + if (devstat_getdevs(NULL, &stats) == -1) { + PyErr_Format(PyExc_RuntimeError, "devstat_getdevs() failed"); + goto error; + } + + for (i = 0; i < stats.dinfo->numdevs; i++) { + py_disk_info = NULL; + struct devstat current; + char disk_name[128]; + current = stats.dinfo->devices[i]; + snprintf(disk_name, sizeof(disk_name), "%s%d", + current.device_name, + current.unit_number); + + py_disk_info = Py_BuildValue( + "(KKKKLL)", + current.operations[DEVSTAT_READ], // no reads + current.operations[DEVSTAT_WRITE], // no writes + current.bytes[DEVSTAT_READ], // bytes read + current.bytes[DEVSTAT_WRITE], // bytes written + (long long)devstat_compute_etime( + ¤t.duration[DEVSTAT_READ], NULL), // r time + (long long)devstat_compute_etime( + ¤t.duration[DEVSTAT_WRITE], NULL)); // w time + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, disk_name, py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + + if (stats.dinfo->mem_ptr) + free(stats.dinfo->mem_ptr); + free(stats.dinfo); + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (stats.dinfo != NULL) + free(stats.dinfo); + return NULL; +} + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) +{ + PyObject *ret_list = PyList_New(0); + PyObject *tuple = NULL; + + if (ret_list == NULL) + return NULL; + +#if __FreeBSD_version < 900000 + struct utmp ut; + FILE *fp; + + fp = fopen(_PATH_UTMP, "r"); + if (fp == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + while (fread(&ut, sizeof(ut), 1, fp) == 1) { + if (*ut.ut_name == '\0') + continue; + tuple = Py_BuildValue( + "(sssf)", + ut.ut_name, // username + ut.ut_line, // tty + ut.ut_host, // hostname + (float)ut.ut_time); // start time + if (!tuple) { + fclose(fp); + goto error; + } + if (PyList_Append(ret_list, tuple)) { + fclose(fp); + goto error; + } + Py_DECREF(tuple); + } + + fclose(fp); +#else + struct utmpx *utx; + + while ((utx = getutxent()) != NULL) { + if (utx->ut_type != USER_PROCESS) + continue; + tuple = Py_BuildValue( + "(sssf)", + utx->ut_user, // username + utx->ut_line, // tty + utx->ut_host, // hostname + (float)utx->ut_tv.tv_sec // start time + ); + + if (!tuple) { + endutxent(); + goto error; + } + if (PyList_Append(ret_list, tuple)) { + endutxent(); + goto error; + } + Py_DECREF(tuple); + } + + endutxent(); +#endif + return ret_list; + +error: + Py_XDECREF(tuple); + Py_DECREF(ret_list); + return NULL; +} + + + +/* + * System-wide open connections. + */ + +#define HASHSIZE 1009 +static struct xfile *psutil_xfiles; +static int psutil_nxfiles; + +int +psutil_populate_xfiles() +{ + size_t len; + + if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) { + PyErr_NoMemory(); + return 0; + } + while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) { + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + return 0; + } + len *= 2; + if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) { + PyErr_NoMemory(); + return 0; + } + } + if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) { + PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch"); + return 0; + } + psutil_nxfiles = len / sizeof *psutil_xfiles; + return 1; +} + +int +psutil_get_pid_from_sock(int sock_hash) +{ + struct xfile *xf; + int hash, n; + for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) { + if (xf->xf_data == NULL) + continue; + hash = (int)((uintptr_t)xf->xf_data % HASHSIZE); + if (sock_hash == hash) + return xf->xf_pid; + } + return -1; +} + + +// Reference: +// https://gitorious.org/freebsd/freebsd/source/ +// f1d6f4778d2044502209708bc167c05f9aa48615:usr.bin/sockstat/sockstat.c +int psutil_gather_inet(int proto, PyObject *py_retlist) +{ + struct xinpgen *xig, *exig; + struct xinpcb *xip; + struct xtcpcb *xtp; + struct inpcb *inp; + struct xsocket *so; + const char *varname = NULL; + size_t len, bufsize; + void *buf; + int hash; + int retry; + int type; + + PyObject *tuple = NULL; + PyObject *laddr = NULL; + PyObject *raddr = NULL; + + switch (proto) { + case IPPROTO_TCP: + varname = "net.inet.tcp.pcblist"; + type = SOCK_STREAM; + break; + case IPPROTO_UDP: + varname = "net.inet.udp.pcblist"; + type = SOCK_DGRAM; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) + continue; // XXX + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xig = (struct xinpgen *)buf; + exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig); + if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xig->xig_gen != exig->xig_gen && retry--); + + + for (;;) { + int lport, rport, pid, status, family; + + xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len); + if (xig >= exig) + break; + + switch (proto) { + case IPPROTO_TCP: + xtp = (struct xtcpcb *)xig; + if (xtp->xt_len != sizeof *xtp) { + PyErr_Format(PyExc_RuntimeError, + "struct xtcpcb size mismatch"); + goto error; + } + inp = &xtp->xt_inp; + so = &xtp->xt_socket; + status = xtp->xt_tp.t_state; + break; + case IPPROTO_UDP: + xip = (struct xinpcb *)xig; + if (xip->xi_len != sizeof *xip) { + PyErr_Format(PyExc_RuntimeError, + "struct xinpcb size mismatch"); + goto error; + } + inp = &xip->xi_inp; + so = &xip->xi_socket; + status = PSUTIL_CONN_NONE; + break; + default: + PyErr_Format(PyExc_RuntimeError, "invalid proto"); + goto error; + } + + char lip[200], rip[200]; + + hash = (int)((uintptr_t)so->xso_so % HASHSIZE); + pid = psutil_get_pid_from_sock(hash); + if (pid < 0) + continue; + lport = ntohs(inp->inp_lport); + rport = ntohs(inp->inp_fport); + + if (inp->inp_vflag & INP_IPV4) { + family = AF_INET; + inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip)); + inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip)); + } + else if (inp->inp_vflag & INP_IPV6) { + family = AF_INET6; + inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip)); + inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip)); + } + + // construct python tuple/list + laddr = Py_BuildValue("(si)", lip, lport); + if (!laddr) + goto error; + if (rport != 0) + raddr = Py_BuildValue("(si)", rip, rport); + else + raddr = Py_BuildValue("()"); + if (!raddr) + goto error; + tuple = Py_BuildValue("(iiiNNii)", -1, family, type, laddr, raddr, + status, pid); + if (!tuple) + goto error; + if (PyList_Append(py_retlist, tuple)) + goto error; + Py_DECREF(tuple); + } + + free(buf); + return 1; + +error: + Py_XDECREF(tuple); + Py_XDECREF(laddr); + Py_XDECREF(raddr); + free(buf); + return 0; +} + + +int psutil_gather_unix(int proto, PyObject *py_retlist) +{ + struct xunpgen *xug, *exug; + struct xunpcb *xup; + const char *varname = NULL; + const char *protoname = NULL; + size_t len; + size_t bufsize; + void *buf; + int hash; + int retry; + int pid; + struct sockaddr_un *sun; + char path[PATH_MAX]; + + PyObject *tuple = NULL; + PyObject *laddr = NULL; + PyObject *raddr = NULL; + + switch (proto) { + case SOCK_STREAM: + varname = "net.local.stream.pcblist"; + protoname = "stream"; + break; + case SOCK_DGRAM: + varname = "net.local.dgram.pcblist"; + protoname = "dgram"; + break; + } + + buf = NULL; + bufsize = 8192; + retry = 5; + + do { + for (;;) { + buf = realloc(buf, bufsize); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + len = bufsize; + if (sysctlbyname(varname, buf, &len, NULL, 0) == 0) + break; + if (errno != ENOMEM) { + PyErr_SetFromErrno(0); + goto error; + } + bufsize *= 2; + } + xug = (struct xunpgen *)buf; + exug = (struct xunpgen *)(void *) + ((char *)buf + len - sizeof *exug); + if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) { + PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch"); + goto error; + } + } while (xug->xug_gen != exug->xug_gen && retry--); + + for (;;) { + xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len); + if (xug >= exug) + break; + xup = (struct xunpcb *)xug; + if (xup->xu_len != sizeof *xup) + goto error; + + hash = (int)((uintptr_t) xup->xu_socket.xso_so % HASHSIZE); + pid = psutil_get_pid_from_sock(hash); + if (pid < 0) + continue; + + sun = (struct sockaddr_un *)&xup->xu_addr; + snprintf(path, sizeof(path), "%.*s", + (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))), + sun->sun_path); + + tuple = Py_BuildValue("(iiisOii)", -1, AF_UNIX, proto, path, Py_None, + PSUTIL_CONN_NONE, pid); + if (!tuple) + goto error; + if (PyList_Append(py_retlist, tuple)) + goto error; + Py_DECREF(tuple); + Py_INCREF(Py_None); + } + + free(buf); + return 1; + +error: + Py_XDECREF(tuple); + Py_XDECREF(laddr); + Py_XDECREF(raddr); + free(buf); + return 0; +} + + +/* + * Return system-wide open connections. + */ +static PyObject* +psutil_net_connections(PyObject* self, PyObject* args) +{ + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (psutil_populate_xfiles() != 1) + goto error; + if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0) + goto error; + if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0) + goto error; + if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0) + goto error; + + free(psutil_xfiles); + return py_retlist; + +error: + Py_DECREF(py_retlist); + free(psutil_xfiles); + return NULL; +} + + +/* + * Get process CPU affinity. + * Reference: http://sources.freebsd.org/RELENG_9/src/usr.bin/cpuset/cpuset.c + */ +static PyObject* +psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args) +{ + long pid; + int ret; + int i; + cpuset_t mask; + PyObject* py_retlist; + PyObject* py_cpu_num; + + if (!PyArg_ParseTuple(args, "i", &pid)) + return NULL; + ret = cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, + sizeof(mask), &mask); + if (ret != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + py_retlist = PyList_New(0); + if (py_retlist == NULL) + return NULL; + + for (i = 0; i < CPU_SETSIZE; i++) { + if (CPU_ISSET(i, &mask)) { + py_cpu_num = Py_BuildValue("i", i); + if (py_cpu_num == NULL) + goto error; + if (PyList_Append(py_retlist, py_cpu_num)) + goto error; + } + } + + return py_retlist; + +error: + Py_XDECREF(py_cpu_num); + Py_DECREF(py_retlist); + return NULL; +} + + +/* + * Set process CPU affinity. + * Reference: http://sources.freebsd.org/RELENG_9/src/usr.bin/cpuset/cpuset.c + */ +static PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) +{ + long pid; + int i; + int seq_len; + int ret; + cpuset_t cpu_set; + PyObject *py_cpu_set; + PyObject *py_cpu_seq = NULL; + + if (!PyArg_ParseTuple(args, "lO", &pid, &py_cpu_set)) + return NULL; + + py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer"); + if (!py_cpu_seq) + return NULL; + seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq); + + // calculate the mask + 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()) + goto error; + CPU_SET(value, &cpu_set); + } + + // set affinity + ret = cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, pid, + sizeof(cpu_set), &cpu_set); + if (ret != 0) { + 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; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + // --- per-process functions + + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Return connections opened by process"}, + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return process pathname executable"}, + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_ppid", psutil_proc_ppid, METH_VARARGS, + "Return process ppid as an integer"}, + {"proc_uids", psutil_proc_uids, METH_VARARGS, + "Return process real effective and saved user ids as a Python tuple"}, + {"proc_gids", psutil_proc_gids, METH_VARARGS, + "Return process real effective and saved group ids as a Python tuple"}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return tuple of user/kern time for the given PID"}, + {"proc_create_time", psutil_proc_create_time, METH_VARARGS, + "Return a float indicating the process create time expressed in " + "seconds since the epoch"}, + {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS, + "Return extended memory info for a process as a Python tuple."}, + {"proc_num_threads", psutil_proc_num_threads, METH_VARARGS, + "Return number of threads used by process"}, + {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Return the number of context switches performed by process"}, + {"proc_threads", psutil_proc_threads, METH_VARARGS, + "Return process threads"}, + {"proc_status", psutil_proc_status, METH_VARARGS, + "Return process status as an integer"}, + {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS, + "Return process IO counters"}, + {"proc_tty_nr", psutil_proc_tty_nr, METH_VARARGS, + "Return process tty (terminal) number"}, + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, + "Return process CPU affinity."}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, + "Set process CPU affinity."}, +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 + {"proc_open_files", psutil_proc_open_files, METH_VARARGS, + "Return files opened by process as a list of (path, fd) tuples"}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory."}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return a list of tuples for every process's memory map"}, + {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, + "Return the number of file descriptors opened by this process"}, +#endif + + // --- 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 an XML string to determine the number physical CPUs."}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS, + "Return system virtual memory usage statistics"}, + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return swap mem stats"}, + {"cpu_times", psutil_cpu_times, METH_VARARGS, + "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-cpu times as a list of tuples"}, +#endif + {"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 a Python dict of tuples for disk I/O information"}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users as a list of tuples"}, + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return system-wide open connections."}, + + {NULL, NULL, 0, NULL} +}; + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_bsd_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_bsd_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef + moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_bsd", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_bsd_traverse, + psutil_bsd_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_bsd(void) + +#else +#define INITERROR return + +void init_psutil_bsd(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_bsd", PsutilMethods); +#endif + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + // process status constants + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); + PyModule_AddIntConstant(module, "SRUN", SRUN); + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SWAIT", SWAIT); + PyModule_AddIntConstant(module, "SLOCK", SLOCK); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + // connection status constants + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/python/psutil/psutil/_psutil_bsd.h b/python/psutil/psutil/_psutil_bsd.h new file mode 100644 index 0000000000..803957dac7 --- /dev/null +++ b/python/psutil/psutil/_psutil_bsd.h @@ -0,0 +1,53 @@ +/* + * 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> + +// --- per-process functions + +static PyObject* psutil_proc_cmdline(PyObject* self, PyObject* args); +static PyObject* psutil_proc_connections(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_proc_create_time(PyObject* self, PyObject* args); +static PyObject* psutil_proc_exe(PyObject* self, PyObject* args); +static PyObject* psutil_proc_gids(PyObject* self, PyObject* args); +static PyObject* psutil_proc_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_info(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args); +static PyObject* psutil_proc_name(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_ctx_switches(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); +static PyObject* psutil_proc_ppid(PyObject* self, PyObject* args); +static PyObject* psutil_proc_status(PyObject* self, PyObject* args); +static PyObject* psutil_proc_threads(PyObject* self, PyObject* args); +static PyObject* psutil_proc_tty_nr(PyObject* self, PyObject* args); +static PyObject* psutil_proc_uids(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args); + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +static PyObject* psutil_proc_open_files(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cwd(PyObject* self, PyObject* args); +#endif + +// --- system-related functions + +static PyObject* psutil_boot_time(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_logical(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); +static PyObject* psutil_net_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_pids(PyObject* self, PyObject* args); +static PyObject* psutil_swap_mem(PyObject* self, PyObject* args); +static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800000 +static PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); +#endif diff --git a/python/psutil/psutil/_psutil_common.c b/python/psutil/psutil/_psutil_common.c new file mode 100644 index 0000000000..1c530d4df5 --- /dev/null +++ b/python/psutil/psutil/_psutil_common.c @@ -0,0 +1,37 @@ +/* + * 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. + * + * Routines common to all platforms. + */ + +#include <Python.h> + + +/* + * Set OSError(errno=ESRCH, strerror="No such process") Python exception. + */ +PyObject * +NoSuchProcess(void) { + PyObject *exc; + char *msg = strerror(ESRCH); + exc = PyObject_CallFunction(PyExc_OSError, "(is)", ESRCH, msg); + PyErr_SetObject(PyExc_OSError, exc); + Py_XDECREF(exc); + return NULL; +} + + +/* + * Set OSError(errno=EACCES, strerror="Permission denied") Python exception. + */ +PyObject * +AccessDenied(void) { + PyObject *exc; + char *msg = strerror(EACCES); + exc = PyObject_CallFunction(PyExc_OSError, "(is)", EACCES, msg); + PyErr_SetObject(PyExc_OSError, exc); + Py_XDECREF(exc); + return NULL; +} diff --git a/python/psutil/psutil/_psutil_common.h b/python/psutil/psutil/_psutil_common.h new file mode 100644 index 0000000000..43021a72df --- /dev/null +++ b/python/psutil/psutil/_psutil_common.h @@ -0,0 +1,10 @@ +/* + * 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* AccessDenied(void); +PyObject* NoSuchProcess(void); diff --git a/python/psutil/psutil/_psutil_linux.c b/python/psutil/psutil/_psutil_linux.c new file mode 100644 index 0000000000..a3bf5643cc --- /dev/null +++ b/python/psutil/psutil/_psutil_linux.c @@ -0,0 +1,689 @@ +/* + * 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. + * + * Linux-specific functions. + */ + +#ifndef _GNU_SOURCE + #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 <linux/ethtool.h> + +#include "_psutil_linux.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) + +// Linux >= 2.6.36 (supposedly) and glibc >= 13 +#define PSUTIL_HAVE_PRLIMIT \ + (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) && \ + (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 13) && \ + defined(__NR_prlimit64) + +#if PSUTIL_HAVE_PRLIMIT + #define _FILE_OFFSET_BITS 64 + #include <time.h> + #include <sys/resource.h> +#endif + + +#if PSUTIL_HAVE_IOPRIO +enum { + IOPRIO_WHO_PROCESS = 1, +}; + +// May happen on old RedHat versions, see: +// https://github.com/giampaolo/psutil/issues/607 +#ifndef DUPLEX_UNKNOWN + #define DUPLEX_UNKNOWN 0xff +#endif + +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) +{ + long pid; + int ioprio, ioclass, iodata; + if (! PyArg_ParseTuple(args, "l", &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) +{ + long pid; + int ioprio, ioclass, iodata; + int retval; + + if (! PyArg_ParseTuple(args, "lii", &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 + + +#if PSUTIL_HAVE_PRLIMIT +/* + * A wrapper around prlimit(2); sets process resource limits. + * This can be used for both get and set, in which case extra + * 'soft' and 'hard' args must be provided. + */ +static PyObject * +psutil_linux_prlimit(PyObject *self, PyObject *args) +{ + long pid; + int ret, resource; + struct rlimit old, new; + struct rlimit *newp = NULL; + PyObject *soft = NULL; + PyObject *hard = NULL; + + if (! PyArg_ParseTuple(args, "li|OO", &pid, &resource, &soft, &hard)) + return NULL; + + // get + if (soft == NULL && hard == NULL) { + ret = prlimit(pid, resource, NULL, &old); + if (ret == -1) + return PyErr_SetFromErrno(PyExc_OSError); +#if defined(PSUTIL_HAVE_LONG_LONG) + if (sizeof(old.rlim_cur) > sizeof(long)) { + return Py_BuildValue("LL", + (PY_LONG_LONG)old.rlim_cur, + (PY_LONG_LONG)old.rlim_max); + } +#endif + return Py_BuildValue("ll", (long)old.rlim_cur, (long)old.rlim_max); + } + + // set + else { +#if defined(PSUTIL_HAVE_LARGEFILE_SUPPORT) + new.rlim_cur = PyLong_AsLongLong(soft); + if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; + new.rlim_max = PyLong_AsLongLong(hard); + if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; +#else + new.rlim_cur = PyLong_AsLong(soft); + if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; + new.rlim_max = PyLong_AsLong(hard); + if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred()) + return NULL; +#endif + newp = &new; + ret = prlimit(pid, resource, newp, &old); + if (ret == -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; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + + // MOUNTED constant comes from mntent.h and it's == '/etc/mtab' + Py_BEGIN_ALLOW_THREADS + file = setmntent(MOUNTED, "r"); + Py_END_ALLOW_THREADS + if ((file == 0) || (file == NULL)) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, MOUNTED); + goto error; + } + + while ((entry = getmntent(file))) { + if (entry == NULL) { + PyErr_Format(PyExc_RuntimeError, "getmntent() failed"); + goto error; + } + py_tuple = Py_BuildValue("(ssss)", + entry->mnt_fsname, // device + entry->mnt_dir, // 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_DECREF(py_tuple); + } + endmntent(file); + return py_retlist; + +error: + if (file != NULL) + endmntent(file); + 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( + "(KKKKKK)", + (unsigned long long)info.totalram * info.mem_unit, // total + (unsigned long long)info.freeram * info.mem_unit, // free + (unsigned long long)info.bufferram * info.mem_unit, // buffer + (unsigned long long)info.sharedram * info.mem_unit, // shared + (unsigned long long)info.totalswap * info.mem_unit, // swap tot + (unsigned long long)info.freeswap * info.mem_unit); // swap free +} + + +/* + * Return process CPU affinity as a Python list + * The dual implementation exists because of: + * https://github.com/giampaolo/psutil/issues/536 + */ + +#ifdef CPU_ALLOC + +static PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) +{ + int cpu, ncpus, count, cpucount_s; + long pid; + size_t setsize; + cpu_set_t *mask = NULL; + PyObject *res = NULL; + + if (!PyArg_ParseTuple(args, "i", &pid)) + return NULL; + ncpus = NCPUS_START; + while (1) { + setsize = CPU_ALLOC_SIZE(ncpus); + mask = CPU_ALLOC(ncpus); + if (mask == NULL) + 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; + } + + res = PyList_New(0); + if (res == 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(res, cpu_num)) { + Py_DECREF(cpu_num); + goto error; + } + Py_DECREF(cpu_num); + --count; + } + } + CPU_FREE(mask); + return res; + +error: + if (mask) + CPU_FREE(mask); + Py_XDECREF(res); + return NULL; +} +#else + + +/* + * Alternative implementation in case CPU_ALLOC is not defined. + */ +static PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) +{ + cpu_set_t cpuset; + unsigned int len = sizeof(cpu_set_t); + long pid; + int i; + PyObject* py_retlist = NULL; + PyObject *py_cpu_num = NULL; + + if (!PyArg_ParseTuple(args, "i", &pid)) + return NULL; + CPU_ZERO(&cpuset); + if (sched_getaffinity(pid, len, &cpuset) < 0) + return PyErr_SetFromErrno(PyExc_OSError); + + py_retlist = PyList_New(0); + if (py_retlist == NULL) + goto error; + for (i = 0; i < CPU_SETSIZE; ++i) { + if (CPU_ISSET(i, &cpuset)) { + py_cpu_num = Py_BuildValue("i", i); + if (py_cpu_num == NULL) + goto error; + if (PyList_Append(py_retlist, py_cpu_num)) + goto error; + Py_DECREF(py_cpu_num); + } + } + + return py_retlist; + +error: + Py_XDECREF(py_cpu_num); + Py_DECREF(py_retlist); + return NULL; +} +#endif + +/* + * 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; + long pid; + int i, seq_len; + PyObject *py_cpu_set; + PyObject *py_cpu_seq = NULL; + + if (!PyArg_ParseTuple(args, "lO", &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()) + 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; +} + + +/* + * Return currently connected users as a list of tuples. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) +{ + PyObject *ret_list = PyList_New(0); + PyObject *tuple = NULL; + PyObject *user_proc = NULL; + struct utmp *ut; + + if (ret_list == NULL) + return NULL; + setutent(); + while (NULL != (ut = getutent())) { + tuple = NULL; + user_proc = NULL; + if (ut->ut_type == USER_PROCESS) + user_proc = Py_True; + else + user_proc = Py_False; + tuple = Py_BuildValue( + "(sssfO)", + ut->ut_user, // username + ut->ut_line, // tty + ut->ut_host, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + user_proc // (bool) user process + ); + if (! tuple) + goto error; + if (PyList_Append(ret_list, tuple)) + goto error; + Py_DECREF(tuple); + } + endutent(); + return ret_list; + +error: + Py_XDECREF(tuple); + Py_XDECREF(user_proc); + Py_DECREF(ret_list); + 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_stats(PyObject* self, PyObject* args) +{ + char *nic_name; + int sock = 0; + int ret; + int duplex; + int speed; + int mtu; + struct ifreq ifr; + struct ethtool_cmd ethcmd; + PyObject *py_is_up = NULL; + PyObject *py_ret = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // is up? + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) + goto error; + if ((ifr.ifr_flags & IFF_UP) != 0) + py_is_up = Py_True; + else + py_is_up = Py_False; + Py_INCREF(py_is_up); + + // MTU + ret = ioctl(sock, SIOCGIFMTU, &ifr); + if (ret == -1) + goto error; + mtu = ifr.ifr_mtu; + + // duplex and speed + memset(ðcmd, 0, sizeof ethcmd); + ethcmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (caddr_t)ðcmd; + ret = ioctl(sock, SIOCETHTOOL, &ifr); + + if (ret != -1) { + duplex = ethcmd.duplex; + speed = ethcmd.speed; + } + else { + if (errno == EOPNOTSUPP) { + // we typically get here in case of wi-fi cards + duplex = DUPLEX_UNKNOWN; + speed = 0; + } + else { + goto error; + } + } + + close(sock); + py_ret = Py_BuildValue("[Oiii]", py_is_up, duplex, speed, mtu); + if (!py_ret) + goto error; + Py_DECREF(py_is_up); + return py_ret; + +error: + Py_XDECREF(py_is_up); + if (sock != 0) + close(sock); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* + * Define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + // --- 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"}, +#endif + {"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."}, + + // --- 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_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats (isup, duplex, speed, mtu)"}, + + // --- linux specific + + {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS, + "A wrapper around sysinfo(), return system memory usage statistics"}, +#if PSUTIL_HAVE_PRLIMIT + {"linux_prlimit", psutil_linux_prlimit, METH_VARARGS, + "Get or set process resource limits."}, +#endif + + + {NULL, NULL, 0, NULL} +}; + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_linux_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_linux_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef + moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_linux", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_linux_traverse, + psutil_linux_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_linux(void) + +#else +#define INITERROR return + +void init_psutil_linux(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_linux", PsutilMethods); +#endif + + + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); +#if PSUTIL_HAVE_PRLIMIT + PyModule_AddIntConstant(module, "RLIM_INFINITY", RLIM_INFINITY); + PyModule_AddIntConstant(module, "RLIMIT_AS", RLIMIT_AS); + PyModule_AddIntConstant(module, "RLIMIT_CORE", RLIMIT_CORE); + PyModule_AddIntConstant(module, "RLIMIT_CPU", RLIMIT_CPU); + PyModule_AddIntConstant(module, "RLIMIT_DATA", RLIMIT_DATA); + PyModule_AddIntConstant(module, "RLIMIT_FSIZE", RLIMIT_FSIZE); + PyModule_AddIntConstant(module, "RLIMIT_LOCKS", RLIMIT_LOCKS); + PyModule_AddIntConstant(module, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK); + PyModule_AddIntConstant(module, "RLIMIT_NOFILE", RLIMIT_NOFILE); + PyModule_AddIntConstant(module, "RLIMIT_NPROC", RLIMIT_NPROC); + PyModule_AddIntConstant(module, "RLIMIT_RSS", RLIMIT_RSS); + PyModule_AddIntConstant(module, "RLIMIT_STACK", RLIMIT_STACK); +#ifdef RLIMIT_MSGQUEUE + PyModule_AddIntConstant(module, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE); +#endif +#ifdef RLIMIT_NICE + PyModule_AddIntConstant(module, "RLIMIT_NICE", RLIMIT_NICE); +#endif +#ifdef RLIMIT_RTPRIO + PyModule_AddIntConstant(module, "RLIMIT_RTPRIO", RLIMIT_RTPRIO); +#endif +#ifdef RLIMIT_RTTIME + PyModule_AddIntConstant(module, "RLIMIT_RTTIME", RLIMIT_RTTIME); +#endif +#ifdef RLIMIT_SIGPENDING + PyModule_AddIntConstant(module, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING); +#endif +#endif + PyModule_AddIntConstant(module, "DUPLEX_HALF", DUPLEX_HALF); + PyModule_AddIntConstant(module, "DUPLEX_FULL", DUPLEX_FULL); + PyModule_AddIntConstant(module, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/python/psutil/psutil/_psutil_linux.h b/python/psutil/psutil/_psutil_linux.h new file mode 100644 index 0000000000..ec6a338719 --- /dev/null +++ b/python/psutil/psutil/_psutil_linux.h @@ -0,0 +1,21 @@ +/* + * 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> + +// process + +static PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args); +static PyObject* psutil_proc_ioprio_get(PyObject* self, PyObject* args); +static PyObject* psutil_proc_ioprio_get(PyObject* self, PyObject* args); + +// system + +static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); +static PyObject* psutil_linux_sysinfo(PyObject* self, PyObject* args); +static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_stats(PyObject* self, PyObject* args); diff --git a/python/psutil/psutil/_psutil_osx.c b/python/psutil/psutil/_psutil_osx.c new file mode 100644 index 0000000000..3ebf8ff272 --- /dev/null +++ b/python/psutil/psutil/_psutil_osx.c @@ -0,0 +1,1808 @@ +/* + * 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. + * + * OS X platform-specific module methods for _psutil_osx + */ + +#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 <netinet/tcp_fsm.h> +#include <arpa/inet.h> +#include <net/if_dl.h> +#include <pwd.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 "_psutil_osx.h" +#include "_psutil_common.h" +#include "arch/osx/process_info.h" + + +/* + * 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() failed: %s", mach_error_string(ret)); + return 0; + } + mach_port_deallocate(mach_task_self(), mport); + return 1; +} + + +/* + * 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 *pid = NULL; + PyObject *retlist = PyList_New(0); + + if (retlist == NULL) + return NULL; + + if (psutil_get_proc_list(&proclist, &num_processes) != 0) { + PyErr_SetString(PyExc_RuntimeError, + "failed to retrieve process list."); + goto error; + } + + if (num_processes > 0) { + // save the address of proclist so we can free it later + orig_address = proclist; + for (idx = 0; idx < num_processes; idx++) { + pid = Py_BuildValue("i", proclist->kp_proc.p_pid); + if (!pid) + goto error; + if (PyList_Append(retlist, pid)) + goto error; + Py_DECREF(pid); + proclist++; + } + free(orig_address); + } + return retlist; + +error: + Py_XDECREF(pid); + Py_DECREF(retlist); + if (orig_address != NULL) + free(orig_address); + return NULL; +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("s", kp.kp_proc.p_comm); +} + + +/* + * Return process current working directory. + */ +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) +{ + long pid; + struct proc_vnodepathinfo pathinfo; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + if (! psutil_proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, &pathinfo, + sizeof(pathinfo))) + { + return NULL; + } + return Py_BuildValue("s", pathinfo.pvi_cdir.vip_path); +} + + +/* + * Return path of the process executable. + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) +{ + long pid; + char buf[PATH_MAX]; + int ret; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + ret = proc_pidpath(pid, &buf, sizeof(buf)); + if (ret == 0) { + if (! psutil_pid_exists(pid)) + return NoSuchProcess(); + else + return AccessDenied(); + } + return Py_BuildValue("s", buf); +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) +{ + long pid; + PyObject *arglist = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + // get the commandline, defined in arch/osx/process_info.c + arglist = psutil_get_arg_list(pid); + return arglist; +} + + +/* + * Return process parent pid from kinfo_proc as a Python integer. + */ +static PyObject * +psutil_proc_ppid(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("l", (long)kp.kp_eproc.e_ppid); +} + + +/* + * Return process real uid from kinfo_proc as a Python integer. + */ +static PyObject * +psutil_proc_uids(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("lll", + (long)kp.kp_eproc.e_pcred.p_ruid, + (long)kp.kp_eproc.e_ucred.cr_uid, + (long)kp.kp_eproc.e_pcred.p_svuid); +} + + +/* + * Return process real group id from ki_comm as a Python integer. + */ +static PyObject * +psutil_proc_gids(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("lll", + (long)kp.kp_eproc.e_pcred.p_rgid, + (long)kp.kp_eproc.e_ucred.cr_groups[0], + (long)kp.kp_eproc.e_pcred.p_svgid); +} + + +/* + * Return process controlling terminal number as an integer. + */ +static PyObject * +psutil_proc_tty_nr(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("i", kp.kp_eproc.e_tdev); +} + + +/* + * Return a list of tuples for every process memory maps. + * 'procstat' cmdline utility has been used as an example. + */ +static PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) +{ + char buf[PATH_MAX]; + char addr_str[34]; + char perms[8]; + int pagesize = getpagesize(); + long pid; + kern_return_t err = KERN_SUCCESS; + mach_port_t task = MACH_PORT_NULL; + uint32_t depth = 1; + vm_address_t address = 0; + vm_size_t size = 0; + + PyObject *py_tuple = NULL; + PyObject *py_list = PyList_New(0); + + if (py_list == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + err = task_for_pid(mach_task_self(), pid, &task); + + if (err != KERN_SUCCESS) { + if (! psutil_pid_exists(pid)) { + NoSuchProcess(); + } + else { + // pid exists, so return AccessDenied error since task_for_pid() + // failed + AccessDenied(); + } + goto error; + } + + while (1) { + py_tuple = NULL; + struct vm_region_submap_info_64 info; + mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; + + err = vm_region_recurse_64(task, &address, &size, &depth, + (vm_region_info_64_t)&info, &count); + if (err == KERN_INVALID_ADDRESS) + break; + if (info.is_submap) { + depth++; + } + else { + // Free/Reset the char[]s to avoid weird paths + memset(buf, 0, sizeof(buf)); + memset(addr_str, 0, sizeof(addr_str)); + memset(perms, 0, sizeof(perms)); + + sprintf(addr_str, "%016lx-%016lx", address, address + size); + sprintf(perms, "%c%c%c/%c%c%c", + (info.protection & VM_PROT_READ) ? 'r' : '-', + (info.protection & VM_PROT_WRITE) ? 'w' : '-', + (info.protection & VM_PROT_EXECUTE) ? 'x' : '-', + (info.max_protection & VM_PROT_READ) ? 'r' : '-', + (info.max_protection & VM_PROT_WRITE) ? 'w' : '-', + (info.max_protection & VM_PROT_EXECUTE) ? 'x' : '-'); + + err = proc_regionfilename(pid, address, buf, sizeof(buf)); + + if (info.share_mode == SM_COW && info.ref_count == 1) { + // Treat single reference SM_COW as SM_PRIVATE + info.share_mode = SM_PRIVATE; + } + + if (strlen(buf) == 0) { + switch (info.share_mode) { + // case SM_LARGE_PAGE: + // Treat SM_LARGE_PAGE the same as SM_PRIVATE + // since they are not shareable and are wired. + case SM_COW: + strcpy(buf, "[cow]"); + break; + case SM_PRIVATE: + strcpy(buf, "[prv]"); + break; + case SM_EMPTY: + strcpy(buf, "[nul]"); + break; + case SM_SHARED: + case SM_TRUESHARED: + strcpy(buf, "[shm]"); + break; + case SM_PRIVATE_ALIASED: + strcpy(buf, "[ali]"); + break; + case SM_SHARED_ALIASED: + strcpy(buf, "[s/a]"); + break; + default: + strcpy(buf, "[???]"); + } + } + + py_tuple = Py_BuildValue( + "sssIIIIIH", + addr_str, // "start-end"address + perms, // "rwx" permissions + buf, // path + info.pages_resident * pagesize, // rss + info.pages_shared_now_private * pagesize, // private + info.pages_swapped_out * pagesize, // swapped + info.pages_dirtied * pagesize, // dirtied + info.ref_count, // ref count + info.shadow_depth // shadow depth + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_list, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + + // increment address for the next map/file + address += size; + } + + if (task != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), task); + + return py_list; + +error: + if (task != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), task); + Py_XDECREF(py_tuple); + Py_DECREF(py_list); + return NULL; +} + + +/* + * 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); +} + + +/* + * 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); +} + + +#define TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) + +/* + * Return a Python tuple (user_time, kernel_time) + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) +{ + long pid; + struct proc_taskinfo pti; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, &pti, sizeof(pti))) + return NULL; + return Py_BuildValue("(dd)", + (float)pti.pti_total_user / 1000000000.0, + (float)pti.pti_total_system / 1000000000.0); +} + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_create_time(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("d", TV2DOUBLE(kp.kp_proc.p_starttime)); +} + + +/* + * Return extended memory info about a process. + */ +static PyObject * +psutil_proc_memory_info(PyObject *self, PyObject *args) +{ + long pid; + struct proc_taskinfo pti; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, &pti, sizeof(pti))) + return NULL; + // Note: determining other memory stats on OSX 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, &pri, sizeof(pri)) + return Py_BuildValue( + "(KKkk)", + pti.pti_resident_size, // resident memory size (rss) + pti.pti_virtual_size, // virtual memory size (vms) + pti.pti_faults, // number of page faults (pages) + pti.pti_pageins // number of actual pageins (pages) + ); +} + + +/* + * Return number of threads used by process as a Python integer. + */ +static PyObject * +psutil_proc_num_threads(PyObject *self, PyObject *args) +{ + long pid; + struct proc_taskinfo pti; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, &pti, sizeof(pti))) + return NULL; + return Py_BuildValue("k", pti.pti_threadnum); +} + + +/* + * Return the number of context switches performed by process. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) +{ + long pid; + struct proc_taskinfo pti; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, &pti, sizeof(pti))) + return NULL; + // 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). + return Py_BuildValue("ki", pti.pti_csw, 0); +} + + +/* + * Return system virtual memory stats + */ +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; + int pagesize = getpagesize(); + // physical mem + mib[0] = CTL_HW; + mib[1] = 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) failed"); + return NULL; + } + + // vm + if (!psutil_sys_vminfo(&vm)) + return NULL; + + return Py_BuildValue( + "KKKKK", + total, + (unsigned long long) vm.active_count * pagesize, + (unsigned long long) vm.inactive_count * pagesize, + (unsigned long long) vm.wire_count * pagesize, + (unsigned long long) vm.free_count * pagesize + ); +} + + +/* + * 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; + int pagesize = 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) 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, + "Error in host_statistics(): %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; + 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 i, 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, "Error in host_processor_info(): %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_DECREF(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; +} + + +/* + * 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) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + 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; + long len; + uint64_t flags; + char opts[400]; + struct statfs *fs = NULL; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + 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_tuple = Py_BuildValue( + "(ssss)", fs[i].f_mntfromname, // device + fs[i].f_mntonname, // 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_DECREF(py_tuple); + } + + free(fs); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (fs != NULL) + free(fs); + return NULL; +} + + +/* + * Return process status as a Python integer. + */ +static PyObject * +psutil_proc_status(PyObject *self, PyObject *args) +{ + long pid; + struct kinfo_proc kp; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return Py_BuildValue("i", (int)kp.kp_proc.p_stat); +} + + +/* + * Return process threads + */ +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) +{ + long pid; + int err, j, 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; + + PyObject *retList = PyList_New(0); + PyObject *pyTuple = NULL; + + if (retList == NULL) + return NULL; + + // the argument passed should be a process id + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + // task_for_pid() requires special privileges + err = task_for_pid(mach_task_self(), pid, &task); + if (err != KERN_SUCCESS) { + if (! psutil_pid_exists(pid)) + NoSuchProcess(); + else + AccessDenied(); + 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(); + } + else { + // otherwise throw a runtime error with appropriate error code + PyErr_Format(PyExc_RuntimeError, + "task_info(TASK_BASIC_INFO) failed"); + } + goto error; + } + + err = task_threads(task, &thread_list, &thread_count); + if (err != KERN_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "task_threads() failed"); + goto error; + } + + for (j = 0; j < thread_count; j++) { + pyTuple = NULL; + 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() with flag THREAD_BASIC_INFO failed"); + goto error; + } + + basic_info_th = (thread_basic_info_t)thinfo_basic; + pyTuple = Py_BuildValue( + "Iff", + j + 1, + (float)basic_info_th->user_time.microseconds / 1000000.0, + (float)basic_info_th->system_time.microseconds / 1000000.0 + ); + if (!pyTuple) + goto error; + if (PyList_Append(retList, pyTuple)) + goto error; + Py_DECREF(pyTuple); + } + + 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 retList; + +error: + if (task != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), task); + Py_XDECREF(pyTuple); + Py_DECREF(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) +{ + long pid; + int pidinfo_result; + int iterations; + int i; + int nb; + + struct proc_fdinfo *fds_pointer = NULL; + struct proc_fdinfo *fdp_pointer; + struct vnode_fdinfowithpath vi; + + PyObject *retList = PyList_New(0); + PyObject *tuple = NULL; + + if (retList == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + + pidinfo_result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (pidinfo_result <= 0) { + // may be be ignored later if errno != 0 + PyErr_Format(PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDLISTFDS) failed"); + goto error; + } + + fds_pointer = malloc(pidinfo_result); + if (fds_pointer == NULL) { + PyErr_NoMemory(); + goto error; + } + pidinfo_result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds_pointer, + pidinfo_result); + if (pidinfo_result <= 0) { + // may be be ignored later if errno != 0 + PyErr_Format(PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDLISTFDS) failed"); + goto error; + } + + iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); + + for (i = 0; i < iterations; i++) { + tuple = NULL; + fdp_pointer = &fds_pointer[i]; + + if (fdp_pointer->proc_fdtype == PROX_FDTYPE_VNODE) + { + nb = proc_pidfdinfo(pid, + fdp_pointer->proc_fd, + PROC_PIDFDVNODEPATHINFO, + &vi, + sizeof(vi)); + + // --- errors checking + if (nb <= 0) { + 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; + } + // may be be ignored later if errno != 0 + PyErr_Format(PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDFDVNODEPATHINFO) failed"); + goto error; + } + if (nb < sizeof(vi)) { + PyErr_Format(PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDFDVNODEPATHINFO) failed " + "(buffer mismatch)"); + goto error; + } + // --- /errors checking + + // --- construct python list + tuple = Py_BuildValue("(si)", + vi.pvip.vip_path, + (int)fdp_pointer->proc_fd); + if (!tuple) + goto error; + if (PyList_Append(retList, tuple)) + goto error; + Py_DECREF(tuple); + // --- /construct python list + } + } + + free(fds_pointer); + return retList; + +error: + Py_XDECREF(tuple); + Py_DECREF(retList); + if (fds_pointer != NULL) + free(fds_pointer); + if (errno != 0) + return PyErr_SetFromErrno(PyExc_OSError); + else if (! psutil_pid_exists(pid)) + return NoSuchProcess(); + else + return NULL; // exception has already been set earlier +} + + +// a signaler for connections without an actual status +static int PSUTIL_CONN_NONE = 128; + +/* + * Return process TCP and UDP connections as a list of tuples. + * 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) +{ + long pid; + int pidinfo_result; + int iterations; + int i; + int nb; + + struct proc_fdinfo *fds_pointer = NULL; + struct proc_fdinfo *fdp_pointer; + struct socket_fdinfo si; + + PyObject *retList = PyList_New(0); + PyObject *tuple = NULL; + PyObject *laddr = NULL; + PyObject *raddr = NULL; + PyObject *af_filter = NULL; + PyObject *type_filter = NULL; + + if (retList == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "lOO", &pid, &af_filter, &type_filter)) + goto error; + + if (!PySequence_Check(af_filter) || !PySequence_Check(type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + if (pid == 0) + return retList; + pidinfo_result = 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 = 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++) { + tuple = NULL; + laddr = NULL; + raddr = NULL; + errno = 0; + fdp_pointer = &fds_pointer[i]; + + if (fdp_pointer->proc_fdtype == PROX_FDTYPE_SOCKET) + { + nb = proc_pidfdinfo(pid, fdp_pointer->proc_fd, + PROC_PIDFDSOCKETINFO, &si, sizeof(si)); + + // --- errors checking + if (nb <= 0) { + if (errno == EBADF) { + // let's assume socket has been closed + continue; + } + if (errno != 0) + PyErr_SetFromErrno(PyExc_OSError); + else + PyErr_Format( + PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDFDVNODEPATHINFO) failed"); + goto error; + } + if (nb < sizeof(si)) { + PyErr_Format(PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDFDVNODEPATHINFO) failed " + "(buffer mismatch)"); + goto error; + } + // --- /errors checking + + // + int fd, family, type, lport, rport, state; + char lip[200], rip[200]; + int inseq; + PyObject *_family; + PyObject *_type; + + fd = (int)fdp_pointer->proc_fd; + family = si.psi.soi_family; + type = si.psi.soi_type; + + // apply filters + _family = PyLong_FromLong((long)family); + inseq = PySequence_Contains(af_filter, _family); + Py_DECREF(_family); + if (inseq == 0) + continue; + _type = PyLong_FromLong((long)type); + inseq = PySequence_Contains(type_filter, _type); + Py_DECREF(_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_SetFromErrno(PyExc_OSError); + 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; + + laddr = Py_BuildValue("(si)", lip, lport); + if (!laddr) + goto error; + if (rport != 0) + raddr = Py_BuildValue("(si)", rip, rport); + else + raddr = Py_BuildValue("()"); + if (!raddr) + goto error; + + // construct the python list + tuple = Py_BuildValue("(iiiNNi)", fd, family, type, laddr, + raddr, state); + if (!tuple) + goto error; + if (PyList_Append(retList, tuple)) + goto error; + Py_DECREF(tuple); + } + else if (family == AF_UNIX) { + // construct the python list + tuple = Py_BuildValue( + "(iiissi)", + fd, family, type, + si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path, + si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path, + PSUTIL_CONN_NONE); + if (!tuple) + goto error; + if (PyList_Append(retList, tuple)) + goto error; + Py_DECREF(tuple); + } + } + } + + free(fds_pointer); + return retList; + +error: + Py_XDECREF(tuple); + Py_XDECREF(laddr); + Py_XDECREF(raddr); + Py_DECREF(retList); + + if (fds_pointer != NULL) + free(fds_pointer); + if (errno != 0) + return PyErr_SetFromErrno(PyExc_OSError); + else if (! psutil_pid_exists(pid)) + return NoSuchProcess(); + else + return PyErr_Format(PyExc_RuntimeError, + "proc_pidinfo(PROC_PIDLISTFDS) failed"); +} + + +/* + * Return number of file descriptors opened by process. + */ +static PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) +{ + long pid; + int pidinfo_result; + int num; + struct proc_fdinfo *fds_pointer; + + if (! PyArg_ParseTuple(args, "l", &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]; + size_t len; + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + + if (py_retdict == NULL) + return NULL; + + 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; + + 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_DECREF(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_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + + 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 OS X 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_DECREF(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 *ret_list = PyList_New(0); + PyObject *tuple = NULL; + + if (ret_list == NULL) + return NULL; + while ((utx = getutxent()) != NULL) { + if (utx->ut_type != USER_PROCESS) + continue; + tuple = Py_BuildValue( + "(sssf)", + utx->ut_user, // username + utx->ut_line, // tty + utx->ut_host, // hostname + (float)utx->ut_tv.tv_sec // start time + ); + if (!tuple) { + endutxent(); + goto error; + } + if (PyList_Append(ret_list, tuple)) { + endutxent(); + goto error; + } + Py_DECREF(tuple); + } + + endutxent(); + return ret_list; + +error: + Py_XDECREF(tuple); + Py_DECREF(ret_list); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + // --- per-process functions + + {"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_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_ppid", psutil_proc_ppid, METH_VARARGS, + "Return process ppid as an integer"}, + {"proc_uids", psutil_proc_uids, METH_VARARGS, + "Return process real user id as an integer"}, + {"proc_gids", psutil_proc_gids, METH_VARARGS, + "Return process real group id as an integer"}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return tuple of user/kern time for the given PID"}, + {"proc_create_time", psutil_proc_create_time, METH_VARARGS, + "Return a float indicating the process create time expressed in " + "seconds since the epoch"}, + {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS, + "Return memory information about a process"}, + {"proc_num_threads", psutil_proc_num_threads, METH_VARARGS, + "Return number of threads used by process"}, + {"proc_status", psutil_proc_status, METH_VARARGS, + "Return process status as an integer"}, + {"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_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Return the number of context switches performed by process"}, + {"proc_connections", psutil_proc_connections, METH_VARARGS, + "Get process TCP and UDP connections as a list of tuples"}, + {"proc_tty_nr", psutil_proc_tty_nr, METH_VARARGS, + "Return process tty number as an integer"}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return a list of tuples for every process's memory map"}, + + // --- 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"}, + {"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"}, + + {NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_osx_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_osx_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_osx", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_osx_traverse, + psutil_osx_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_osx(void) + +#else +#define INITERROR return + +void +init_psutil_osx(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_osx", PsutilMethods); +#endif + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + // process status constants, defined in: + // http://fxr.watson.org/fxr/source/bsd/sys/proc.h?v=xnu-792.6.70#L149 + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SRUN", SRUN); + PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + // connection status constants + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RECEIVED", TCPS_SYN_RECEIVED); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/python/psutil/psutil/_psutil_osx.h b/python/psutil/psutil/_psutil_osx.h new file mode 100644 index 0000000000..907a8e5373 --- /dev/null +++ b/python/psutil/psutil/_psutil_osx.h @@ -0,0 +1,41 @@ +/* + * 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> + +// --- per-process functions +static PyObject* psutil_proc_cmdline(PyObject* self, PyObject* args); +static PyObject* psutil_proc_connections(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_proc_create_time(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cwd(PyObject* self, PyObject* args); +static PyObject* psutil_proc_exe(PyObject* self, PyObject* args); +static PyObject* psutil_proc_gids(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_info(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args); +static PyObject* psutil_proc_name(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args); +static PyObject* psutil_proc_open_files(PyObject* self, PyObject* args); +static PyObject* psutil_proc_ppid(PyObject* self, PyObject* args); +static PyObject* psutil_proc_status(PyObject* self, PyObject* args); +static PyObject* psutil_proc_threads(PyObject* self, PyObject* args); +static PyObject* psutil_proc_tty_nr(PyObject* self, PyObject* args); +static PyObject* psutil_proc_uids(PyObject* self, PyObject* args); + +// --- system-related functions +static PyObject* psutil_boot_time(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_logical(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); +static PyObject* psutil_net_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_pids(PyObject* self, PyObject* args); +static PyObject* psutil_swap_mem(PyObject* self, PyObject* args); +static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); diff --git a/python/psutil/psutil/_psutil_posix.c b/python/psutil/psutil/_psutil_posix.c new file mode 100644 index 0000000000..183dab0e12 --- /dev/null +++ b/python/psutil/psutil/_psutil_posix.c @@ -0,0 +1,531 @@ +/* + * 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. + * + * Functions specific to all POSIX compliant platforms. + */ + +#include <Python.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <ifaddrs.h> + +#ifdef __linux +#include <netdb.h> +#include <linux/if_packet.h> +#endif // end linux + +#if defined(__FreeBSD__) || defined(__APPLE__) +#include <netdb.h> +#include <netinet/in.h> +#include <net/if_dl.h> +#endif + +#if defined(__sun) +#include <netdb.h> +#endif + +#include "_psutil_posix.h" + + +/* + * Given a PID return process priority as a Python integer. + */ +static PyObject * +psutil_posix_getpriority(PyObject *self, PyObject *args) +{ + long pid; + int priority; + errno = 0; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + priority = getpriority(PRIO_PROCESS, pid); + if (errno != 0) + return PyErr_SetFromErrno(PyExc_OSError); + return Py_BuildValue("i", priority); +} + + +/* + * Given a PID and a value change process priority. + */ +static PyObject * +psutil_posix_setpriority(PyObject *self, PyObject *args) +{ + long pid; + int priority; + int retval; + + if (! PyArg_ParseTuple(args, "li", &pid, &priority)) + return NULL; + retval = setpriority(PRIO_PROCESS, pid, priority); + if (retval == -1) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; +} + + +/* + * Translate a sockaddr struct into a Python string. + * Return None if address family is not AF_INET* or AF_PACKET. + */ +static PyObject * +psutil_convert_ipaddr(struct sockaddr *addr, int family) +{ + char buf[NI_MAXHOST]; + int err; + int addrlen; + int n; + size_t len; + const char *data; + char *ptr; + + if (addr == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + else if (family == AF_INET || family == AF_INET6) { + if (family == AF_INET) + addrlen = sizeof(struct sockaddr_in); + else + addrlen = sizeof(struct sockaddr_in6); + err = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST); + if (err != 0) { + // XXX we get here on FreeBSD when processing 'lo' / AF_INET6 + // broadcast. Not sure what to do other than returning None. + // ifconfig does not show anything BTW. + //PyErr_Format(PyExc_RuntimeError, gai_strerror(err)); + //return NULL; + Py_INCREF(Py_None); + return Py_None; + } + else { + return Py_BuildValue("s", buf); + } + } +#ifdef __linux + else if (family == AF_PACKET) { + struct sockaddr_ll *lladdr = (struct sockaddr_ll *)addr; + len = lladdr->sll_halen; + data = (const char *)lladdr->sll_addr; + } +#endif +#if defined(__FreeBSD__) || defined(__APPLE__) + else if (addr->sa_family == AF_LINK) { + // Note: prior to Python 3.4 socket module does not expose + // AF_LINK so we'll do. + struct sockaddr_dl *dladdr = (struct sockaddr_dl *)addr; + len = dladdr->sdl_alen; + data = LLADDR(dladdr); + } +#endif + else { + // unknown family + Py_INCREF(Py_None); + return Py_None; + } + + // AF_PACKET or AF_LINK + if (len > 0) { + ptr = buf; + for (n = 0; n < len; ++n) { + sprintf(ptr, "%02x:", data[n] & 0xff); + ptr += 3; + } + *--ptr = '\0'; + return Py_BuildValue("s", buf); + } + else { + Py_INCREF(Py_None); + return Py_None; + } +} + + +/* + * Return NICs information a-la ifconfig as a list of tuples. + * TODO: on Solaris we won't get any MAC address. + */ +static PyObject* +psutil_net_if_addrs(PyObject* self, PyObject* args) +{ + struct ifaddrs *ifaddr, *ifa; + int family; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_netmask = NULL; + PyObject *py_broadcast = NULL; + + if (py_retlist == NULL) + return NULL; + if (getifaddrs(&ifaddr) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) + continue; + family = ifa->ifa_addr->sa_family; + py_address = psutil_convert_ipaddr(ifa->ifa_addr, family); + // If the primary address can't be determined just skip it. + // I've never seen this happen on Linux but I did on FreeBSD. + if (py_address == Py_None) + continue; + if (py_address == NULL) + goto error; + py_netmask = psutil_convert_ipaddr(ifa->ifa_netmask, family); + if (py_netmask == NULL) + goto error; +#ifdef __linux + py_broadcast = psutil_convert_ipaddr(ifa->ifa_ifu.ifu_broadaddr, family); +#else + py_broadcast = psutil_convert_ipaddr(ifa->ifa_broadaddr, family); +#endif + if (py_broadcast == NULL) + goto error; + py_tuple = Py_BuildValue( + "(siOOO)", + ifa->ifa_name, + family, + py_address, + py_netmask, + py_broadcast + ); + + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_address); + Py_DECREF(py_netmask); + Py_DECREF(py_broadcast); + } + + freeifaddrs(ifaddr); + return py_retlist; + +error: + if (ifaddr != NULL) + freeifaddrs(ifaddr); + Py_DECREF(py_retlist); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_XDECREF(py_netmask); + Py_XDECREF(py_broadcast); + return NULL; +} + + +/* + * net_if_stats() implementation. This is here because it is common + * to both OSX and FreeBSD and I didn't know where else to put it. + */ +#if defined(__FreeBSD__) || defined(__APPLE__) + +#include <sys/sockio.h> +#include <net/if_media.h> +#include <net/if.h> + +int psutil_get_nic_speed(int ifm_active) { + // Determine NIC speed. Taken from: + // http://www.i-scream.org/libstatgrab/ + // Assuming only ETHER devices + switch(IFM_TYPE(ifm_active)) { + case IFM_ETHER: + switch(IFM_SUBTYPE(ifm_active)) { +#if defined(IFM_HPNA_1) && ((!defined(IFM_10G_LR)) \ + || (IFM_10G_LR != IFM_HPNA_1)) + // HomePNA 1.0 (1Mb/s) + case(IFM_HPNA_1): + return 1; +#endif + // 10 Mbit + case(IFM_10_T): // 10BaseT - RJ45 + case(IFM_10_2): // 10Base2 - Thinnet + case(IFM_10_5): // 10Base5 - AUI + case(IFM_10_STP): // 10BaseT over shielded TP + case(IFM_10_FL): // 10baseFL - Fiber + return 10; + // 100 Mbit + case(IFM_100_TX): // 100BaseTX - RJ45 + case(IFM_100_FX): // 100BaseFX - Fiber + case(IFM_100_T4): // 100BaseT4 - 4 pair cat 3 + case(IFM_100_VG): // 100VG-AnyLAN + case(IFM_100_T2): // 100BaseT2 + return 100; + // 1000 Mbit + case(IFM_1000_SX): // 1000BaseSX - multi-mode fiber + case(IFM_1000_LX): // 1000baseLX - single-mode fiber + case(IFM_1000_CX): // 1000baseCX - 150ohm STP +#if defined(IFM_1000_TX) && !defined(OPENBSD) + // FreeBSD 4 and others (but NOT OpenBSD)? + case(IFM_1000_TX): +#endif +#ifdef IFM_1000_FX + case(IFM_1000_FX): +#endif +#ifdef IFM_1000_T + case(IFM_1000_T): +#endif + return 1000; +#if defined(IFM_10G_SR) || defined(IFM_10G_LR) || defined(IFM_10G_CX4) \ + || defined(IFM_10G_T) +#ifdef IFM_10G_SR + case(IFM_10G_SR): +#endif +#ifdef IFM_10G_LR + case(IFM_10G_LR): +#endif +#ifdef IFM_10G_CX4 + case(IFM_10G_CX4): +#endif +#ifdef IFM_10G_TWINAX + case(IFM_10G_TWINAX): +#endif +#ifdef IFM_10G_TWINAX_LONG + case(IFM_10G_TWINAX_LONG): +#endif +#ifdef IFM_10G_T + case(IFM_10G_T): +#endif + return 10000; +#endif +#if defined(IFM_2500_SX) +#ifdef IFM_2500_SX + case(IFM_2500_SX): +#endif + return 2500; +#endif // any 2.5GBit stuff... + // We don't know what it is + default: + return 0; + } + break; + +#ifdef IFM_TOKEN + case IFM_TOKEN: + switch(IFM_SUBTYPE(ifm_active)) { + case IFM_TOK_STP4: // Shielded twisted pair 4m - DB9 + case IFM_TOK_UTP4: // Unshielded twisted pair 4m - RJ45 + return 4; + case IFM_TOK_STP16: // Shielded twisted pair 16m - DB9 + case IFM_TOK_UTP16: // Unshielded twisted pair 16m - RJ45 + return 16; +#if defined(IFM_TOK_STP100) || defined(IFM_TOK_UTP100) +#ifdef IFM_TOK_STP100 + case IFM_TOK_STP100: // Shielded twisted pair 100m - DB9 +#endif +#ifdef IFM_TOK_UTP100 + case IFM_TOK_UTP100: // Unshielded twisted pair 100m - RJ45 +#endif + return 100; +#endif + // We don't know what it is + default: + return 0; + } + break; +#endif + +#ifdef IFM_FDDI + case IFM_FDDI: + switch(IFM_SUBTYPE(ifm_active)) { + // We don't know what it is + default: + return 0; + } + break; +#endif + case IFM_IEEE80211: + switch(IFM_SUBTYPE(ifm_active)) { + case IFM_IEEE80211_FH1: // Frequency Hopping 1Mbps + case IFM_IEEE80211_DS1: // Direct Sequence 1Mbps + return 1; + case IFM_IEEE80211_FH2: // Frequency Hopping 2Mbps + case IFM_IEEE80211_DS2: // Direct Sequence 2Mbps + return 2; + case IFM_IEEE80211_DS5: // Direct Sequence 5Mbps + return 5; + case IFM_IEEE80211_DS11: // Direct Sequence 11Mbps + return 11; + case IFM_IEEE80211_DS22: // Direct Sequence 22Mbps + return 22; + // We don't know what it is + default: + return 0; + } + break; + + default: + return 0; + } +} + + +/* + * Return stats about a particular network interface. + * References: + * http://www.i-scream.org/libstatgrab/ + */ +static PyObject * +psutil_net_if_stats(PyObject *self, PyObject *args) +{ + char *nic_name; + int sock = 0; + int ret; + int duplex; + int speed; + int mtu; + struct ifreq ifr; + struct ifmediareq ifmed; + + PyObject *py_is_up = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // is up? + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) + goto error; + if ((ifr.ifr_flags & IFF_UP) != 0) + py_is_up = Py_True; + else + py_is_up = Py_False; + Py_INCREF(py_is_up); + + // MTU + ret = ioctl(sock, SIOCGIFMTU, &ifr); + if (ret == -1) + goto error; + mtu = ifr.ifr_mtu; + + // speed / duplex + memset(&ifmed, 0, sizeof(struct ifmediareq)); + strlcpy(ifmed.ifm_name, nic_name, sizeof(ifmed.ifm_name)); + ret = ioctl(sock, SIOCGIFMEDIA, (caddr_t)&ifmed); + if (ret == -1) { + speed = 0; + duplex = 0; + } + else { + speed = psutil_get_nic_speed(ifmed.ifm_active); + if ((ifmed.ifm_active | IFM_FDX) == ifmed.ifm_active) + duplex = 2; + else if ((ifmed.ifm_active | IFM_HDX) == ifmed.ifm_active) + duplex = 1; + else + duplex = 0; + } + + close(sock); + Py_DECREF(py_is_up); + + return Py_BuildValue("[Oiii]", py_is_up, duplex, speed, mtu); + +error: + Py_XDECREF(py_is_up); + if (sock != 0) + close(sock); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} +#endif // net_if_stats() implementation + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + {"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"}, +#if defined(__FreeBSD__) || defined(__APPLE__) + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats."}, +#endif + {NULL, NULL, 0, NULL} +}; + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_posix_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_posix_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_posix", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_posix_traverse, + psutil_posix_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_posix(void) + +#else +#define INITERROR return + +void init_psutil_posix(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods); +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__sun) + PyModule_AddIntConstant(module, "AF_LINK", AF_LINK); +#endif + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/python/psutil/psutil/_psutil_posix.h b/python/psutil/psutil/_psutil_posix.h new file mode 100644 index 0000000000..bbe6fc5ad2 --- /dev/null +++ b/python/psutil/psutil/_psutil_posix.h @@ -0,0 +1,15 @@ +/* + * 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> + +static PyObject* psutil_net_if_addrs(PyObject* self, PyObject* args); +static PyObject* psutil_posix_getpriority(PyObject* self, PyObject* args); +static PyObject* psutil_posix_setpriority(PyObject* self, PyObject* args); + +#if defined(__FreeBSD__) || defined(__APPLE__) +static PyObject* psutil_net_if_stats(PyObject* self, PyObject* args); +#endif diff --git a/python/psutil/psutil/_psutil_sunos.c b/python/psutil/psutil/_psutil_sunos.c new file mode 100644 index 0000000000..0cb6978f27 --- /dev/null +++ b/python/psutil/psutil/_psutil_sunos.c @@ -0,0 +1,1389 @@ +/* + * 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. + * + * Functions specific to Sun OS Solaris platforms. + * + * Thanks to Justin Venus who originally wrote a consistent part of + * this in Cython which I later on translated in C. + */ + + +#include <Python.h> + +// fix for "Cannot use procfs in the large file compilation environment" +// error, see: +// http://sourceware.org/ml/gdb-patches/2010-11/msg00336.html +#undef _FILE_OFFSET_BITS +#define _STRUCTURED_PROC 1 + +// fix compilation issue on SunOS 5.10, see: +// https://github.com/giampaolo/psutil/issues/421 +#define NEW_MIB_COMPLIANT + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/proc.h> +#include <sys/swap.h> +#include <sys/sysinfo.h> +#include <sys/mntent.h> // for MNTTAB +#include <sys/mnttab.h> +#include <sys/procfs.h> +#include <sys/sockio.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <utmpx.h> +#include <kstat.h> +#include <sys/ioctl.h> +#include <sys/tihdr.h> +#include <stropts.h> +#include <inet/tcp.h> +#include <arpa/inet.h> +#include <net/if.h> + +#include "_psutil_sunos.h" + + +#define TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec) + +/* + * Read a file content and fills a C structure with it. + */ +int +psutil_file_to_struct(char *path, void *fstruct, size_t size) +{ + int fd; + size_t nbytes; + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + return 0; + } + nbytes = read(fd, fstruct, size); + if (nbytes <= 0) { + close(fd); + PyErr_SetFromErrno(PyExc_OSError); + return 0; + } + if (nbytes != size) { + close(fd); + PyErr_SetString(PyExc_RuntimeError, "structure size mismatch"); + return 0; + } + close(fd); + return nbytes; +} + + +/* + * Return process ppid, rss, vms, ctime, nice, nthreads, status and tty + * as a Python tuple. + */ +static PyObject * +psutil_proc_basic_info(PyObject *self, PyObject *args) +{ + int pid; + char path[100]; + psinfo_t info; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/psinfo", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("ikkdiiik", + info.pr_ppid, // parent pid + info.pr_rssize, // rss + info.pr_size, // vms + TV2DOUBLE(info.pr_start), // create time + info.pr_lwp.pr_nice, // nice + info.pr_nlwp, // no. of threads + info.pr_lwp.pr_state, // status code + info.pr_ttydev // tty nr + ); +} + + +/* + * Return process name and args as a Python tuple. + */ +static PyObject * +psutil_proc_name_and_args(PyObject *self, PyObject *args) +{ + int pid; + char path[100]; + psinfo_t info; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/psinfo", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("ss", info.pr_fname, info.pr_psargs); +} + + +/* + * Return process user and system CPU times as a Python tuple. + */ +static PyObject * +psutil_proc_cpu_times(PyObject *self, PyObject *args) +{ + int pid; + char path[100]; + pstatus_t info; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/status", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + // results are more precise than os.times() + return Py_BuildValue("dd", + TV2DOUBLE(info.pr_utime), + TV2DOUBLE(info.pr_stime)); +} + + +/* + * Return process uids/gids as a Python tuple. + */ +static PyObject * +psutil_proc_cred(PyObject *self, PyObject *args) +{ + int pid; + char path[100]; + prcred_t info; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/cred", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("iiiiii", + info.pr_ruid, info.pr_euid, info.pr_suid, + info.pr_rgid, info.pr_egid, info.pr_sgid); +} + + +/* + * Return process uids/gids as a Python tuple. + */ +static PyObject * +psutil_proc_num_ctx_switches(PyObject *self, PyObject *args) +{ + int pid; + char path[100]; + prusage_t info; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/usage", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("kk", info.pr_vctx, info.pr_ictx); +} + + +/* + * Process IO counters. + * + * Commented out and left here as a reminder. Apparently we cannot + * retrieve process IO stats because: + * - 'pr_ioch' is a sum of chars read and written, with no distinction + * - 'pr_inblk' and 'pr_oublk', which should be the number of bytes + * read and written, hardly increase and according to: + * http://www.brendangregg.com/Perf/paper_diskubyp1.pdf + * ...they should be meaningless anyway. + * +static PyObject* +proc_io_counters(PyObject* self, PyObject* args) +{ + int pid; + char path[100]; + prusage_t info; + + if (! PyArg_ParseTuple(args, "i", &pid)) + return NULL; + sprintf(path, "/proc/%i/usage", pid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + + // On Solaris we only have 'pr_ioch' which accounts for bytes read + // *and* written. + // 'pr_inblk' and 'pr_oublk' should be expressed in blocks of + // 8KB according to: + // http://www.brendangregg.com/Perf/paper_diskubyp1.pdf (pag. 8) + return Py_BuildValue("kkkk", + info.pr_ioch, + info.pr_ioch, + info.pr_inblk, + info.pr_oublk); +} + */ + + +/* + * Return information about a given process thread. + */ +static PyObject * +psutil_proc_query_thread(PyObject *self, PyObject *args) +{ + int pid, tid; + char path[100]; + lwpstatus_t info; + + if (! PyArg_ParseTuple(args, "ii", &pid, &tid)) + return NULL; + sprintf(path, "/proc/%i/lwp/%i/lwpstatus", pid, tid); + if (! psutil_file_to_struct(path, (void *)&info, sizeof(info))) + return NULL; + return Py_BuildValue("dd", + TV2DOUBLE(info.pr_utime), + TV2DOUBLE(info.pr_stime)); +} + + +/* + * Return information about system virtual memory. + */ +static PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) +{ +// XXX (arghhh!) +// total/free swap mem: commented out as for some reason I can't +// manage to get the same results shown by "swap -l", despite the +// code below is exactly the same as: +// http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/ +// cmd/swap/swap.c +// We're going to parse "swap -l" output from Python (sigh!) + +/* + struct swaptable *st; + struct swapent *swapent; + int i; + struct stat64 statbuf; + char *path; + char fullpath[MAXPATHLEN+1]; + int num; + + if ((num = swapctl(SC_GETNSWP, NULL)) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + if (num == 0) { + PyErr_SetString(PyExc_RuntimeError, "no swap devices configured"); + return NULL; + } + if ((st = malloc(num * sizeof(swapent_t) + sizeof (int))) == NULL) { + PyErr_SetString(PyExc_RuntimeError, "malloc failed"); + return NULL; + } + if ((path = malloc(num * MAXPATHLEN)) == NULL) { + PyErr_SetString(PyExc_RuntimeError, "malloc failed"); + return NULL; + } + swapent = st->swt_ent; + for (i = 0; i < num; i++, swapent++) { + swapent->ste_path = path; + path += MAXPATHLEN; + } + st->swt_n = num; + if ((num = swapctl(SC_LIST, st)) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + swapent = st->swt_ent; + long t = 0, f = 0; + for (i = 0; i < num; i++, swapent++) { + int diskblks_per_page =(int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT); + t += (long)swapent->ste_pages; + f += (long)swapent->ste_free; + } + + free(st); + return Py_BuildValue("(kk)", t, f); +*/ + + kstat_ctl_t *kc; + kstat_t *k; + cpu_stat_t *cpu; + int cpu_count = 0; + int flag = 0; + uint_t sin = 0; + uint_t sout = 0; + + kc = kstat_open(); + if (kc == NULL) + return PyErr_SetFromErrno(PyExc_OSError);; + + k = kc->kc_chain; + while (k != NULL) { + if ((strncmp(k->ks_name, "cpu_stat", 8) == 0) && \ + (kstat_read(kc, k, NULL) != -1) ) + { + flag = 1; + cpu = (cpu_stat_t *) k->ks_data; + sin += cpu->cpu_vminfo.pgswapin; // num pages swapped in + sout += cpu->cpu_vminfo.pgswapout; // num pages swapped out + } + cpu_count += 1; + k = k->ks_next; + } + kstat_close(kc); + if (!flag) { + PyErr_SetString(PyExc_RuntimeError, "no swap device was found"); + return NULL; + } + return Py_BuildValue("(II)", sin, sout); +} + + +/* + * Return users currently connected on the system. + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) +{ + struct utmpx *ut; + PyObject *ret_list = PyList_New(0); + PyObject *tuple = NULL; + PyObject *user_proc = NULL; + + if (ret_list == NULL) + return NULL; + + while (NULL != (ut = getutxent())) { + if (ut->ut_type == USER_PROCESS) + user_proc = Py_True; + else + user_proc = Py_False; + tuple = Py_BuildValue( + "(sssfO)", + ut->ut_user, // username + ut->ut_line, // tty + ut->ut_host, // hostname + (float)ut->ut_tv.tv_sec, // tstamp + user_proc); // (bool) user process + if (tuple == NULL) + goto error; + if (PyList_Append(ret_list, tuple)) + goto error; + Py_DECREF(tuple); + } + endutent(); + + return ret_list; + +error: + Py_XDECREF(tuple); + Py_DECREF(ret_list); + if (ut != NULL) + endutent(); + return NULL; +} + + +/* + * 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; + struct mnttab mt; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) + return NULL; + + file = fopen(MNTTAB, "rb"); + if (file == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + while (getmntent(file, &mt) == 0) { + py_tuple = Py_BuildValue( + "(ssss)", + mt.mnt_special, // device + mt.mnt_mountp, // mount point + mt.mnt_fstype, // fs type + mt.mnt_mntopts); // options + if (py_tuple == NULL) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + + } + fclose(file); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (file != NULL) + fclose(file); + return NULL; +} + + +/* + * Return system-wide CPU times. + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) +{ + kstat_ctl_t *kc; + kstat_t *ksp; + cpu_stat_t cs; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + kc = kstat_open(); + if (kc == NULL) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (ksp = kc->kc_chain; ksp != NULL; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_stat") == 0) { + if (kstat_read(kc, ksp, &cs) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + py_cputime = Py_BuildValue("ffff", + (float)cs.cpu_sysinfo.cpu[CPU_USER], + (float)cs.cpu_sysinfo.cpu[CPU_KERNEL], + (float)cs.cpu_sysinfo.cpu[CPU_IDLE], + (float)cs.cpu_sysinfo.cpu[CPU_WAIT]); + if (py_cputime == NULL) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_DECREF(py_cputime); + py_cputime = NULL; + } + } + + kstat_close(kc); + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + if (kc != NULL) + kstat_close(kc); + return NULL; +} + + +/* + * Return disk IO statistics. + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) +{ + kstat_ctl_t *kc; + kstat_t *ksp; + kstat_io_t kio; + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + + if (py_retdict == NULL) + return NULL; + kc = kstat_open(); + if (kc == NULL) { + PyErr_SetFromErrno(PyExc_OSError);; + goto error; + } + ksp = kc->kc_chain; + while (ksp != NULL) { + if (ksp->ks_type == KSTAT_TYPE_IO) { + if (strcmp(ksp->ks_class, "disk") == 0) { + if (kstat_read(kc, ksp, &kio) == -1) { + kstat_close(kc); + return PyErr_SetFromErrno(PyExc_OSError);; + } + py_disk_info = Py_BuildValue( + "(IIKKLL)", + kio.reads, + kio.writes, + kio.nread, + kio.nwritten, + kio.rtime / 1000 / 1000, // from nano to milli secs + kio.wtime / 1000 / 1000 // from nano to milli secs + ); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, ksp->ks_name, + py_disk_info)) + goto error; + Py_DECREF(py_disk_info); + } + } + ksp = ksp->ks_next; + } + kstat_close(kc); + + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (kc != NULL) + kstat_close(kc); + return NULL; +} + + +/* + * Return process memory mappings. + */ +static PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) +{ + int pid; + int fd = -1; + char path[100]; + char perms[10]; + char *name; + struct stat st; + pstatus_t status; + + prxmap_t *xmap = NULL, *p; + off_t size; + size_t nread; + int nmap; + uintptr_t pr_addr_sz; + uintptr_t stk_base_sz, brk_base_sz; + + PyObject *pytuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "i", &pid)) + goto error; + + sprintf(path, "/proc/%i/status", pid); + if (! psutil_file_to_struct(path, (void *)&status, sizeof(status))) + goto error; + + sprintf(path, "/proc/%i/xmap", pid); + if (stat(path, &st) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + size = st.st_size; + + fd = open(path, O_RDONLY); + if (fd == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + xmap = (prxmap_t *)malloc(size); + if (xmap == NULL) { + PyErr_NoMemory(); + goto error; + } + + nread = pread(fd, xmap, size, 0); + nmap = nread / sizeof(prxmap_t); + p = xmap; + + while (nmap) { + nmap -= 1; + if (p == NULL) { + p += 1; + continue; + } + + perms[0] = '\0'; + pr_addr_sz = p->pr_vaddr + p->pr_size; + + // perms + sprintf(perms, "%c%c%c%c%c%c", p->pr_mflags & MA_READ ? 'r' : '-', + p->pr_mflags & MA_WRITE ? 'w' : '-', + p->pr_mflags & MA_EXEC ? 'x' : '-', + p->pr_mflags & MA_SHARED ? 's' : '-', + p->pr_mflags & MA_NORESERVE ? 'R' : '-', + p->pr_mflags & MA_RESERVED1 ? '*' : ' '); + + // name + if (strlen(p->pr_mapname) > 0) { + name = p->pr_mapname; + } + else { + if ((p->pr_mflags & MA_ISM) || (p->pr_mflags & MA_SHM)) { + name = "[shmid]"; + } + else { + stk_base_sz = status.pr_stkbase + status.pr_stksize; + brk_base_sz = status.pr_brkbase + status.pr_brksize; + + if ((pr_addr_sz > status.pr_stkbase) && + (p->pr_vaddr < stk_base_sz)) { + name = "[stack]"; + } + else if ((p->pr_mflags & MA_ANON) && \ + (pr_addr_sz > status.pr_brkbase) && \ + (p->pr_vaddr < brk_base_sz)) { + name = "[heap]"; + } + else { + name = "[anon]"; + } + } + } + + pytuple = Py_BuildValue("iisslll", + p->pr_vaddr, + pr_addr_sz, + perms, + name, + (long)p->pr_rss * p->pr_pagesize, + (long)p->pr_anon * p->pr_pagesize, + (long)p->pr_locked * p->pr_pagesize); + if (!pytuple) + goto error; + if (PyList_Append(py_retlist, pytuple)) + goto error; + Py_DECREF(pytuple); + + // increment pointer + p += 1; + } + + close(fd); + free(xmap); + return py_retlist; + +error: + if (fd != -1) + close(fd); + Py_XDECREF(pytuple); + Py_DECREF(py_retlist); + if (xmap != NULL) + free(xmap); + return NULL; +} + + +/* + * Return a list of tuples for network I/O statistics. + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) +{ + kstat_ctl_t *kc = NULL; + kstat_t *ksp; + kstat_named_t *rbytes, *wbytes, *rpkts, *wpkts, *ierrs, *oerrs; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + + if (py_retdict == NULL) + return NULL; + kc = kstat_open(); + if (kc == NULL) + goto error; + + ksp = kc->kc_chain; + while (ksp != NULL) { + if (ksp->ks_type != KSTAT_TYPE_NAMED) + goto next; + if (strcmp(ksp->ks_class, "net") != 0) + goto next; + /* + // XXX "lo" (localhost) interface makes kstat_data_lookup() fail + // (maybe because "ifconfig -a" says it's a virtual interface?). + if ((strcmp(ksp->ks_module, "link") != 0) && + (strcmp(ksp->ks_module, "lo") != 0)) { + goto skip; + */ + if ((strcmp(ksp->ks_module, "link") != 0)) + goto next; + + if (kstat_read(kc, ksp, NULL) == -1) { + errno = 0; + continue; + } + + rbytes = (kstat_named_t *)kstat_data_lookup(ksp, "rbytes"); + wbytes = (kstat_named_t *)kstat_data_lookup(ksp, "obytes"); + rpkts = (kstat_named_t *)kstat_data_lookup(ksp, "ipackets"); + wpkts = (kstat_named_t *)kstat_data_lookup(ksp, "opackets"); + ierrs = (kstat_named_t *)kstat_data_lookup(ksp, "ierrors"); + oerrs = (kstat_named_t *)kstat_data_lookup(ksp, "oerrors"); + + if ((rbytes == NULL) || (wbytes == NULL) || (rpkts == NULL) || + (wpkts == NULL) || (ierrs == NULL) || (oerrs == NULL)) + { + PyErr_SetString(PyExc_RuntimeError, "kstat_data_lookup() failed"); + goto error; + } + +#if defined(_INT64_TYPE) + py_ifc_info = Py_BuildValue("(KKKKkkii)", + wbytes->value.ui64, + rbytes->value.ui64, + wpkts->value.ui64, + rpkts->value.ui64, + ierrs->value.ui32, + oerrs->value.ui32, +#else + py_ifc_info = Py_BuildValue("(kkkkkkii)", + wbytes->value.ui32, + rbytes->value.ui32, + wpkts->value.ui32, + rpkts->value.ui32, + ierrs->value.ui32, + oerrs->value.ui32, +#endif + 0, // dropin not supported + 0 // dropout not supported + ); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ksp->ks_name, py_ifc_info)) + goto error; + Py_DECREF(py_ifc_info); + goto next; + +next: + ksp = ksp->ks_next; + } + + kstat_close(kc); + return py_retdict; + +error: + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (kc != NULL) + kstat_close(kc); + return NULL; +} + + +#ifndef EXPER_IP_AND_ALL_IRES +#define EXPER_IP_AND_ALL_IRES (1024+4) +#endif + +// a signaler for connections without an actual status +static int PSUTIL_CONN_NONE = 128; + +/* + * Return TCP and UDP connections opened by process. + * UNIX sockets are excluded. + * + * Thanks to: + * https://github.com/DavidGriffith/finx/blob/master/ + * nxsensor-3.5.0-1/src/sysdeps/solaris.c + * ...and: + * https://hg.java.net/hg/solaris~on-src/file/tip/usr/src/cmd/ + * cmd-inet/usr.bin/netstat/netstat.c + */ +static PyObject * +psutil_net_connections(PyObject *self, PyObject *args) +{ + long pid; + int sd = 0; + mib2_tcpConnEntry_t *tp = NULL; + mib2_udpEntry_t *ude; +#if defined(AF_INET6) + mib2_tcp6ConnEntry_t *tp6; + mib2_udp6Entry_t *ude6; +#endif + char buf[512]; + int i, flags, getcode, num_ent, state; + char lip[200], rip[200]; + int lport, rport; + int processed_pid; + int databuf_init = 0; + struct strbuf ctlbuf, databuf; + struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf; + struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf; + struct T_error_ack *tea = (struct T_error_ack *)buf; + struct opthdr *mibhdr; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *af_filter = NULL; + PyObject *type_filter = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "lOO", &pid, &af_filter, &type_filter)) + goto error; + if (!PySequence_Check(af_filter) || !PySequence_Check(type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + sd = open("/dev/arp", O_RDWR); + if (sd == -1) { + PyErr_SetFromErrnoWithFilename(PyExc_OSError, "/dev/arp"); + goto error; + } + + /* + XXX - These 2 are used in ifconfig.c but they seem unnecessary + ret = ioctl(sd, I_PUSH, "tcp"); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + ret = ioctl(sd, I_PUSH, "udp"); + if (ret == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + */ + + // OK, this mess is basically copied and pasted from nxsensor project + // which copied and pasted it from netstat source code, mibget() + // function. Also see: + // http://stackoverflow.com/questions/8723598/ + tor->PRIM_type = T_SVR4_OPTMGMT_REQ; + tor->OPT_offset = sizeof (struct T_optmgmt_req); + tor->OPT_length = sizeof (struct opthdr); + tor->MGMT_flags = T_CURRENT; + mibhdr = (struct opthdr *)&tor[1]; + mibhdr->level = EXPER_IP_AND_ALL_IRES; + mibhdr->name = 0; + mibhdr->len = 0; + + ctlbuf.buf = buf; + ctlbuf.len = tor->OPT_offset + tor->OPT_length; + flags = 0; // request to be sent in non-priority + + if (putmsg(sd, &ctlbuf, (struct strbuf *)0, flags) == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + mibhdr = (struct opthdr *)&toa[1]; + ctlbuf.maxlen = sizeof (buf); + + for (;;) { + flags = 0; + getcode = getmsg(sd, &ctlbuf, (struct strbuf *)0, &flags); + + if (getcode != MOREDATA || + ctlbuf.len < sizeof (struct T_optmgmt_ack) || + toa->PRIM_type != T_OPTMGMT_ACK || + toa->MGMT_flags != T_SUCCESS) + { + break; + } + if (ctlbuf.len >= sizeof (struct T_error_ack) && + tea->PRIM_type == T_ERROR_ACK) + { + PyErr_SetString(PyExc_RuntimeError, "ERROR_ACK"); + goto error; + } + if (getcode == 0 && + ctlbuf.len >= sizeof (struct T_optmgmt_ack) && + toa->PRIM_type == T_OPTMGMT_ACK && + toa->MGMT_flags == T_SUCCESS) + { + PyErr_SetString(PyExc_RuntimeError, "ERROR_T_OPTMGMT_ACK"); + goto error; + } + + databuf.maxlen = mibhdr->len; + databuf.len = 0; + databuf.buf = (char *)malloc((int)mibhdr->len); + if (!databuf.buf) { + PyErr_NoMemory(); + goto error; + } + databuf_init = 1; + + flags = 0; + getcode = getmsg(sd, (struct strbuf *)0, &databuf, &flags); + if (getcode < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // TCPv4 + if (mibhdr->level == MIB2_TCP && mibhdr->name == MIB2_TCP_13) { + tp = (mib2_tcpConnEntry_t *)databuf.buf; + num_ent = mibhdr->len / sizeof(mib2_tcpConnEntry_t); + for (i = 0; i < num_ent; i++, tp++) { + processed_pid = tp->tcpConnCreationProcess; + if (pid != -1 && processed_pid != pid) + continue; + // construct local/remote addresses + inet_ntop(AF_INET, &tp->tcpConnLocalAddress, lip, sizeof(lip)); + inet_ntop(AF_INET, &tp->tcpConnRemAddress, rip, sizeof(rip)); + lport = tp->tcpConnLocalPort; + rport = tp->tcpConnRemPort; + + // contruct python tuple/list + 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; + state = tp->tcpConnEntryInfo.ce_state; + + // add item + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET, SOCK_STREAM, + py_laddr, py_raddr, state, + processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } +#if defined(AF_INET6) + // TCPv6 + else if (mibhdr->level == MIB2_TCP6 && mibhdr->name == MIB2_TCP6_CONN) + { + tp6 = (mib2_tcp6ConnEntry_t *)databuf.buf; + num_ent = mibhdr->len / sizeof(mib2_tcp6ConnEntry_t); + + for (i = 0; i < num_ent; i++, tp6++) { + processed_pid = tp6->tcp6ConnCreationProcess; + if (pid != -1 && processed_pid != pid) + continue; + // construct local/remote addresses + inet_ntop(AF_INET6, &tp6->tcp6ConnLocalAddress, lip, sizeof(lip)); + inet_ntop(AF_INET6, &tp6->tcp6ConnRemAddress, rip, sizeof(rip)); + lport = tp6->tcp6ConnLocalPort; + rport = tp6->tcp6ConnRemPort; + + // contruct python tuple/list + 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; + state = tp6->tcp6ConnEntryInfo.ce_state; + + // add item + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET6, SOCK_STREAM, + py_laddr, py_raddr, state, processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } +#endif + // UDPv4 + else if (mibhdr->level == MIB2_UDP || mibhdr->level == MIB2_UDP_ENTRY) { + ude = (mib2_udpEntry_t *)databuf.buf; + num_ent = mibhdr->len / sizeof(mib2_udpEntry_t); + for (i = 0; i < num_ent; i++, ude++) { + processed_pid = ude->udpCreationProcess; + if (pid != -1 && processed_pid != pid) + continue; + // XXX Very ugly hack! It seems we get here only the first + // time we bump into a UDPv4 socket. PID is a very high + // number (clearly impossible) and the address does not + // belong to any valid interface. Not sure what else + // to do other than skipping. + if (processed_pid > 131072) + continue; + inet_ntop(AF_INET, &ude->udpLocalAddress, lip, sizeof(lip)); + lport = ude->udpLocalPort; + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET, SOCK_DGRAM, + py_laddr, py_raddr, PSUTIL_CONN_NONE, + processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } +#if defined(AF_INET6) + // UDPv6 + else if (mibhdr->level == MIB2_UDP6 || + mibhdr->level == MIB2_UDP6_ENTRY) + { + ude6 = (mib2_udp6Entry_t *)databuf.buf; + num_ent = mibhdr->len / sizeof(mib2_udp6Entry_t); + for (i = 0; i < num_ent; i++, ude6++) { + processed_pid = ude6->udp6CreationProcess; + if (pid != -1 && processed_pid != pid) + continue; + inet_ntop(AF_INET6, &ude6->udp6LocalAddress, lip, sizeof(lip)); + lport = ude6->udp6LocalPort; + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + py_tuple = Py_BuildValue("(iiiNNiI)", -1, AF_INET6, SOCK_DGRAM, + py_laddr, py_raddr, PSUTIL_CONN_NONE, + processed_pid); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + } +#endif + free(databuf.buf); + } + + close(sd); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (databuf_init == 1) + free(databuf.buf); + if (sd != 0) + close(sd); + return NULL; +} + + +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) +{ + float boot_time = 0.0; + struct utmpx *ut; + + while (NULL != (ut = getutxent())) { + if (ut->ut_type == BOOT_TIME) { + boot_time = (float)ut->ut_tv.tv_sec; + break; + } + } + endutent(); + if (boot_time != 0.0) { + return Py_BuildValue("f", boot_time); + } + else { + PyErr_SetString(PyExc_RuntimeError, "can't determine boot time"); + return NULL; + } +} + + +/* + * Return the number of physical CPU cores on the system. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) +{ + kstat_ctl_t *kc; + kstat_t *ksp; + int ncpus = 0; + + kc = kstat_open(); + if (kc == NULL) + goto error; + ksp = kstat_lookup(kc, "cpu_info", -1, NULL); + if (ksp == NULL) + goto error; + + for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_module, "cpu_info") != 0) + continue; + if (kstat_read(kc, ksp, NULL) == -1) + goto error; + ncpus += 1; + } + + kstat_close(kc); + if (ncpus > 0) + return Py_BuildValue("i", ncpus); + else + goto error; + +error: + // mimic os.cpu_count() + if (kc != NULL) + kstat_close(kc); + Py_RETURN_NONE; +} + + +/* + * 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_stats(PyObject* self, PyObject* args) +{ + kstat_ctl_t *kc = NULL; + kstat_t *ksp; + kstat_named_t *knp; + int ret; + int sock = 0; + int duplex; + int speed; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + PyObject *py_is_up = NULL; + + if (py_retdict == NULL) + return NULL; + kc = kstat_open(); + if (kc == NULL) + goto error; + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + goto error; + + for (ksp = kc->kc_chain; ksp; ksp = ksp->ks_next) { + if (strcmp(ksp->ks_class, "net") == 0) { + struct ifreq ifr; + + kstat_read(kc, ksp, NULL); + if (ksp->ks_type != KSTAT_TYPE_NAMED) + continue; + if (strcmp(ksp->ks_class, "net") != 0) + continue; + + strncpy(ifr.ifr_name, ksp->ks_name, sizeof(ifr.ifr_name)); + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) + continue; // not a network interface + + // is up? + if ((ifr.ifr_flags & IFF_UP) != 0) { + if ((knp = kstat_data_lookup(ksp, "link_up")) != NULL) { + if (knp->value.ui32 != 0u) + py_is_up = Py_True; + else + py_is_up = Py_False; + } + else { + py_is_up = Py_True; + } + } + else { + py_is_up = Py_False; + } + Py_INCREF(py_is_up); + + // duplex + duplex = 0; // unknown + if ((knp = kstat_data_lookup(ksp, "link_duplex")) != NULL) { + if (knp->value.ui32 == 1) + duplex = 1; // half + else if (knp->value.ui32 == 2) + duplex = 2; // full + } + + // speed + if ((knp = kstat_data_lookup(ksp, "ifspeed")) != NULL) + // expressed in bits per sec, we want mega bits per sec + speed = (int)knp->value.ui64 / 1000000; + else + speed = 0; + + // mtu + ret = ioctl(sock, SIOCGIFMTU, &ifr); + if (ret == -1) + goto error; + + py_ifc_info = Py_BuildValue("(Oiii)", py_is_up, duplex, speed, + ifr.ifr_mtu); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ksp->ks_name, py_ifc_info)) + goto error; + Py_DECREF(py_ifc_info); + } + } + + close(sock); + kstat_close(kc); + return py_retdict; + +error: + Py_XDECREF(py_is_up); + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (sock != 0) + close(sock); + if (kc != NULL) + kstat_close(kc); + PyErr_SetFromErrno(PyExc_OSError); + return NULL; +} + + +/* + * define the psutil C module methods and initialize the module. + */ +static PyMethodDef +PsutilMethods[] = +{ + // --- process-related functions + {"proc_basic_info", psutil_proc_basic_info, METH_VARARGS, + "Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"}, + {"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS, + "Return process name and args."}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return process user and system CPU times."}, + {"proc_cred", psutil_proc_cred, METH_VARARGS, + "Return process uids/gids."}, + {"query_process_thread", psutil_proc_query_thread, METH_VARARGS, + "Return info about a process thread"}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, + "Return process memory mappings"}, + {"proc_num_ctx_switches", psutil_proc_num_ctx_switches, METH_VARARGS, + "Return the number of context switches performed by process"}, + + // --- system-related functions + {"swap_mem", psutil_swap_mem, METH_VARARGS, + "Return information about system swap memory."}, + {"users", psutil_users, METH_VARARGS, + "Return currently connected users."}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS, + "Return disk partitions."}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, + "Return system per-CPU times."}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, + "Return a Python dict of tuples for disk I/O statistics."}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS, + "Return a Python dict of tuples for network I/O statistics."}, + {"boot_time", psutil_boot_time, METH_VARARGS, + "Return system boot time in seconds since the EPOCH."}, + {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, + "Return the number of physical CPUs on the system."}, + {"net_connections", psutil_net_connections, METH_VARARGS, + "Return TCP and UDP syste-wide open connections."}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS, + "Return NIC stats (isup, duplex, speed, mtu)"}, + +{NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int +psutil_sunos_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int +psutil_sunos_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_sunos", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_sunos_traverse, + psutil_sunos_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_sunos(void) + +#else +#define INITERROR return + +void init_psutil_sunos(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_sunos", PsutilMethods); +#endif + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + PyModule_AddIntConstant(module, "SSLEEP", SSLEEP); + PyModule_AddIntConstant(module, "SRUN", SRUN); + PyModule_AddIntConstant(module, "SZOMB", SZOMB); + PyModule_AddIntConstant(module, "SSTOP", SSTOP); + PyModule_AddIntConstant(module, "SIDL", SIDL); + PyModule_AddIntConstant(module, "SONPROC", SONPROC); + PyModule_AddIntConstant(module, "SWAIT", SWAIT); + + PyModule_AddIntConstant(module, "PRNODEV", PRNODEV); // for process tty + + PyModule_AddIntConstant(module, "TCPS_CLOSED", TCPS_CLOSED); + PyModule_AddIntConstant(module, "TCPS_CLOSING", TCPS_CLOSING); + PyModule_AddIntConstant(module, "TCPS_CLOSE_WAIT", TCPS_CLOSE_WAIT); + PyModule_AddIntConstant(module, "TCPS_LISTEN", TCPS_LISTEN); + PyModule_AddIntConstant(module, "TCPS_ESTABLISHED", TCPS_ESTABLISHED); + PyModule_AddIntConstant(module, "TCPS_SYN_SENT", TCPS_SYN_SENT); + PyModule_AddIntConstant(module, "TCPS_SYN_RCVD", TCPS_SYN_RCVD); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_1", TCPS_FIN_WAIT_1); + PyModule_AddIntConstant(module, "TCPS_FIN_WAIT_2", TCPS_FIN_WAIT_2); + PyModule_AddIntConstant(module, "TCPS_LAST_ACK", TCPS_LAST_ACK); + PyModule_AddIntConstant(module, "TCPS_TIME_WAIT", TCPS_TIME_WAIT); + // sunos specific + PyModule_AddIntConstant(module, "TCPS_IDLE", TCPS_IDLE); + // sunos specific + PyModule_AddIntConstant(module, "TCPS_BOUND", TCPS_BOUND); + PyModule_AddIntConstant(module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + + if (module == NULL) + INITERROR; +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/python/psutil/psutil/_psutil_sunos.h b/python/psutil/psutil/_psutil_sunos.h new file mode 100644 index 0000000000..f93dbfe0fd --- /dev/null +++ b/python/psutil/psutil/_psutil_sunos.h @@ -0,0 +1,28 @@ +/* + * 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> + +// processes +static PyObject* psutil_proc_basic_info(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cred(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args); +static PyObject* psutil_proc_name_and_args(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_ctx_switches(PyObject* self, PyObject* args); +static PyObject* psutil_proc_query_thread(PyObject* self, PyObject* args); + +// system +static PyObject* psutil_boot_time(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args); +static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); +static PyObject* psutil_net_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_swap_mem(PyObject* self, PyObject* args); +static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_net_connections(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_stats(PyObject* self, PyObject* args); diff --git a/python/psutil/psutil/_psutil_windows.c b/python/psutil/psutil/_psutil_windows.c new file mode 100644 index 0000000000..3e0f7a7cdc --- /dev/null +++ b/python/psutil/psutil/_psutil_windows.c @@ -0,0 +1,3405 @@ +/* + * 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. + * + * Windows platform-specific module methods for _psutil_windows + */ + +// Fixes clash between winsock2.h and windows.h +#define WIN32_LEAN_AND_MEAN + +#include <Python.h> +#include <windows.h> +#include <Psapi.h> +#include <time.h> +#include <lm.h> +#include <WinIoCtl.h> +#include <tchar.h> +#include <tlhelp32.h> +#include <winsock2.h> +#include <iphlpapi.h> +#include <wtsapi32.h> +#include <ws2tcpip.h> + +// Link with Iphlpapi.lib +#pragma comment(lib, "IPHLPAPI.lib") + +#include "_psutil_windows.h" +#include "_psutil_common.h" +#include "arch/windows/security.h" +#include "arch/windows/process_info.h" +#include "arch/windows/process_handles.h" +#include "arch/windows/ntextapi.h" +#include "arch/windows/inet_ntop.h" + +#ifdef __MINGW32__ +#include "arch/windows/glpi.h" +#endif + + +/* + * ============================================================================ + * Utilities + * ============================================================================ + */ + + // a flag for connections without an actual status +static int PSUTIL_CONN_NONE = 128; + +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) +#define LO_T ((float)1e-7) +#define HI_T (LO_T*4294967296.0) +#define BYTESWAP_USHORT(x) ((((USHORT)(x) << 8) | ((USHORT)(x) >> 8)) & 0xffff) +#ifndef AF_INET6 +#define AF_INET6 23 +#endif +#define _psutil_conn_decref_objs() \ + Py_DECREF(_AF_INET); \ + Py_DECREF(_AF_INET6);\ + Py_DECREF(_SOCK_STREAM);\ + Py_DECREF(_SOCK_DGRAM); + +typedef BOOL (WINAPI *LPFN_GLPI) + (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); + +// fix for mingw32, see +// https://github.com/giampaolo/psutil/issues/351#c2 +typedef struct _DISK_PERFORMANCE_WIN_2008 { + LARGE_INTEGER BytesRead; + LARGE_INTEGER BytesWritten; + LARGE_INTEGER ReadTime; + LARGE_INTEGER WriteTime; + LARGE_INTEGER IdleTime; + DWORD ReadCount; + DWORD WriteCount; + DWORD QueueDepth; + DWORD SplitCount; + LARGE_INTEGER QueryTime; + DWORD StorageDeviceNumber; + WCHAR StorageManagerName[8]; +} DISK_PERFORMANCE_WIN_2008; + +// --- network connections mingw32 support +#ifndef _IPRTRMIB_H +typedef struct _MIB_TCP6ROW_OWNER_PID { + UCHAR ucLocalAddr[16]; + DWORD dwLocalScopeId; + DWORD dwLocalPort; + UCHAR ucRemoteAddr[16]; + DWORD dwRemoteScopeId; + DWORD dwRemotePort; + DWORD dwState; + DWORD dwOwningPid; +} MIB_TCP6ROW_OWNER_PID, *PMIB_TCP6ROW_OWNER_PID; + +typedef struct _MIB_TCP6TABLE_OWNER_PID { + DWORD dwNumEntries; + MIB_TCP6ROW_OWNER_PID table[ANY_SIZE]; +} MIB_TCP6TABLE_OWNER_PID, *PMIB_TCP6TABLE_OWNER_PID; +#endif + +#ifndef __IPHLPAPI_H__ +typedef struct in6_addr { + union { + UCHAR Byte[16]; + USHORT Word[8]; + } u; +} IN6_ADDR, *PIN6_ADDR, FAR *LPIN6_ADDR; + +typedef enum _UDP_TABLE_CLASS { + UDP_TABLE_BASIC, + UDP_TABLE_OWNER_PID, + UDP_TABLE_OWNER_MODULE +} UDP_TABLE_CLASS, *PUDP_TABLE_CLASS; + +typedef struct _MIB_UDPROW_OWNER_PID { + DWORD dwLocalAddr; + DWORD dwLocalPort; + DWORD dwOwningPid; +} MIB_UDPROW_OWNER_PID, *PMIB_UDPROW_OWNER_PID; + +typedef struct _MIB_UDPTABLE_OWNER_PID { + DWORD dwNumEntries; + MIB_UDPROW_OWNER_PID table[ANY_SIZE]; +} MIB_UDPTABLE_OWNER_PID, *PMIB_UDPTABLE_OWNER_PID; +#endif + +typedef struct _MIB_UDP6ROW_OWNER_PID { + UCHAR ucLocalAddr[16]; + DWORD dwLocalScopeId; + DWORD dwLocalPort; + DWORD dwOwningPid; +} MIB_UDP6ROW_OWNER_PID, *PMIB_UDP6ROW_OWNER_PID; + +typedef struct _MIB_UDP6TABLE_OWNER_PID { + DWORD dwNumEntries; + MIB_UDP6ROW_OWNER_PID table[ANY_SIZE]; +} MIB_UDP6TABLE_OWNER_PID, *PMIB_UDP6TABLE_OWNER_PID; + + +PIP_ADAPTER_ADDRESSES +psutil_get_nic_addresses() { + // allocate a 15 KB buffer to start with + int outBufLen = 15000; + DWORD dwRetVal = 0; + ULONG attempts = 0; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + + do { + pAddresses = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen); + if (pAddresses == NULL) { + PyErr_NoMemory(); + return NULL; + } + + dwRetVal = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, pAddresses, + &outBufLen); + if (dwRetVal == ERROR_BUFFER_OVERFLOW) { + free(pAddresses); + pAddresses = NULL; + } + else { + break; + } + + attempts++; + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (attempts < 3)); + + if (dwRetVal != NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "GetAdaptersAddresses() failed."); + return NULL; + } + + return pAddresses; +} + + +/* + * ============================================================================ + * Public Python API + * ============================================================================ + */ + + +/* + * Return a Python float representing the system uptime expressed in seconds + * since the epoch. + */ +static PyObject * +psutil_boot_time(PyObject *self, PyObject *args) +{ + double uptime; + time_t pt; + FILETIME fileTime; + long long ll; + + GetSystemTimeAsFileTime(&fileTime); + + /* + HUGE thanks to: + http://johnstewien.spaces.live.com/blog/cns!E6885DB5CEBABBC8!831.entry + + This function converts the FILETIME structure to the 32 bit + Unix time structure. + The time_t is a 32-bit value for the number of seconds since + January 1, 1970. A FILETIME is a 64-bit for the number of + 100-nanosecond periods since January 1, 1601. Convert by + subtracting the number of 100-nanosecond period betwee 01-01-1970 + and 01-01-1601, from time_t the divide by 1e+7 to get to the same + base granularity. + */ + ll = (((LONGLONG)(fileTime.dwHighDateTime)) << 32) \ + + fileTime.dwLowDateTime; + pt = (time_t)((ll - 116444736000000000ull) / 10000000ull); + + // XXX - By using GetTickCount() time will wrap around to zero if the + // system is run continuously for 49.7 days. + uptime = GetTickCount() / 1000.00f; + return Py_BuildValue("d", (double)pt - uptime); +} + + +/* + * Return 1 if PID exists in the current process list, else 0. + */ +static PyObject * +psutil_pid_exists(PyObject *self, PyObject *args) +{ + long pid; + int status; + + if (! PyArg_ParseTuple(args, "l", &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 *pid = NULL; + PyObject *retlist = PyList_New(0); + + if (retlist == NULL) + return NULL; + proclist = psutil_get_pids(&numberOfReturnedPIDs); + if (proclist == NULL) + goto error; + + for (i = 0; i < numberOfReturnedPIDs; i++) { + pid = Py_BuildValue("I", proclist[i]); + if (!pid) + goto error; + if (PyList_Append(retlist, pid)) + goto error; + Py_DECREF(pid); + } + + // free C array allocated for PIDs + free(proclist); + return retlist; + +error: + Py_XDECREF(pid); + Py_DECREF(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; + long pid; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (pid == 0) + return AccessDenied(); + + hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + // see https://github.com/giampaolo/psutil/issues/24 + NoSuchProcess(); + } + else { + PyErr_SetFromWindowsErr(0); + } + return NULL; + } + + // kill the process + if (! TerminateProcess(hProcess, 0)) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + 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; + long pid; + long timeout; + + if (! PyArg_ParseTuple(args, "ll", &pid, &timeout)) + return NULL; + if (pid == 0) + return AccessDenied(); + + 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_SetFromWindowsErr(0); + return NULL; + } + } + + // wait until the process has terminated + Py_BEGIN_ALLOW_THREADS + retVal = WaitForSingleObject(hProcess, timeout); + Py_END_ALLOW_THREADS + + if (retVal == WAIT_FAILED) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(GetLastError()); + } + if (retVal == WAIT_TIMEOUT) { + CloseHandle(hProcess); + return Py_BuildValue("l", WAIT_TIMEOUT); + } + + // get the exit code; note: subprocess module (erroneously?) uses + // what returned by WaitForSingleObject + if (GetExitCodeProcess(hProcess, &ExitCode) == 0) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(GetLastError()); + } + 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_cpu_times(PyObject *self, PyObject *args) +{ + long pid; + HANDLE hProcess; + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid); + if (hProcess == NULL) + return NULL; + if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { + CloseHandle(hProcess); + if (GetLastError() == ERROR_ACCESS_DENIED) { + // usually means the process has died so we throw a NoSuchProcess + // here + return NoSuchProcess(); + } + else { + PyErr_SetFromWindowsErr(0); + return NULL; + } + } + + CloseHandle(hProcess); + + /* + * User and kernel times are represented as a FILETIME structure + * wich 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( + "(dd)", + (double)(ftUser.dwHighDateTime * 429.4967296 + \ + ftUser.dwLowDateTime * 1e-7), + (double)(ftKernel.dwHighDateTime * 429.4967296 + \ + ftKernel.dwLowDateTime * 1e-7) + ); +} + + +/* + * Return a Python float indicating the process create time expressed in + * seconds since the epoch. + */ +static PyObject * +psutil_proc_create_time(PyObject *self, PyObject *args) +{ + long pid; + long long unix_time; + DWORD exitCode; + HANDLE hProcess; + BOOL ret; + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + // special case for PIDs 0 and 4, return system boot time + if (0 == pid || 4 == pid) + return psutil_boot_time(NULL, NULL); + + hProcess = psutil_handle_from_pid(pid); + if (hProcess == NULL) + return NULL; + if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { + CloseHandle(hProcess); + if (GetLastError() == ERROR_ACCESS_DENIED) { + // usually means the process has died so we throw a + // NoSuchProcess here + return NoSuchProcess(); + } + else { + PyErr_SetFromWindowsErr(0); + return NULL; + } + } + + // Make sure the process is not gone as OpenProcess alone seems to be + // unreliable in doing so (it seems a previous call to p.wait() makes + // it unreliable). + // This check is important as creation time is used to make sure the + // process is still running. + ret = GetExitCodeProcess(hProcess, &exitCode); + CloseHandle(hProcess); + if (ret != 0) { + if (exitCode != STILL_ACTIVE) + return NoSuchProcess(); + } + else { + // Ignore access denied as it means the process is still alive. + // For all other errors, we want an exception. + if (GetLastError() != ERROR_ACCESS_DENIED) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + } + + /* + Convert the FILETIME structure to a Unix time. + It's the best I could find by googling and borrowing code here and there. + The time returned has a precision of 1 second. + */ + unix_time = ((LONGLONG)ftCreate.dwHighDateTime) << 32; + unix_time += ftCreate.dwLowDateTime - 116444736000000000LL; + unix_time /= 10000000; + return Py_BuildValue("d", (double)unix_time); +} + + + +/* + * Return the number of logical CPUs. + */ +static PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) +{ + SYSTEM_INFO system_info; + system_info.dwNumberOfProcessors = 0; + + GetSystemInfo(&system_info); + if (system_info.dwNumberOfProcessors == 0) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("I", system_info.dwNumberOfProcessors); +} + + +/* + * Return the number of physical CPU cores. + */ +static PyObject * +psutil_cpu_count_phys(PyObject *self, PyObject *args) +{ + LPFN_GLPI glpi; + DWORD rc; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; + DWORD length = 0; + DWORD offset = 0; + int ncpus = 0; + + glpi = (LPFN_GLPI)GetProcAddress(GetModuleHandle(TEXT("kernel32")), + "GetLogicalProcessorInformation"); + if (glpi == NULL) + goto return_none; + + while (1) { + rc = glpi(buffer, &length); + if (rc == FALSE) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + if (buffer) + free(buffer); + buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc( + length); + if (NULL == buffer) { + PyErr_NoMemory(); + return NULL; + } + } + else { + goto return_none; + } + } + else { + break; + } + } + + ptr = buffer; + while (offset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= length) { + if (ptr->Relationship == RelationProcessorCore) + ncpus += 1; + offset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); + ptr++; + } + + free(buffer); + if (ncpus == 0) + goto return_none; + else + return Py_BuildValue("i", ncpus); + +return_none: + // mimic os.cpu_count() + if (buffer != NULL) + free(buffer); + Py_RETURN_NONE; +} + + +/* + * Return process cmdline as a Python list of cmdline arguments. + */ +static PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) { + long pid; + int pid_return; + PyObject *arglist; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if ((pid == 0) || (pid == 4)) + return Py_BuildValue("[]"); + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess(); + if (pid_return == -1) + return NULL; + + // XXX the assumptio below probably needs to go away + + // May fail any of several ReadProcessMemory calls etc. and + // not indicate a real problem so we ignore any errors and + // just live without commandline. + arglist = psutil_get_arg_list(pid); + if ( NULL == arglist ) { + // carry on anyway, clear any exceptions too + PyErr_Clear(); + return Py_BuildValue("[]"); + } + + return arglist; +} + + +/* + * Return process executable path. + */ +static PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + long pid; + HANDLE hProcess; + wchar_t exe[MAX_PATH]; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid_waccess(pid, PROCESS_QUERY_INFORMATION); + if (NULL == hProcess) + return NULL; + if (GetProcessImageFileNameW(hProcess, exe, MAX_PATH) == 0) { + CloseHandle(hProcess); + PyErr_SetFromWindowsErr(0); + return NULL; + } + CloseHandle(hProcess); + return Py_BuildValue("u", exe); +} + + +/* + * Return process base name. + * Note: psutil_proc_exe() is attempted first because it's faster + * but it raise AccessDenied for processes owned by other users + * in which case we fall back on using this. + */ +static PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + long pid; + int ok; + PROCESSENTRY32 pentry; + HANDLE hSnapShot; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, pid); + if (hSnapShot == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + pentry.dwSize = sizeof(PROCESSENTRY32); + ok = Process32First(hSnapShot, &pentry); + if (! ok) { + CloseHandle(hSnapShot); + PyErr_SetFromWindowsErr(0); + return NULL; + } + while (ok) { + if (pentry.th32ProcessID == pid) { + CloseHandle(hSnapShot); + return Py_BuildValue("s", pentry.szExeFile); + } + ok = Process32Next(hSnapShot, &pentry); + } + + CloseHandle(hSnapShot); + NoSuchProcess(); + return NULL; +} + + +/* + * Return process memory information as a Python tuple. + */ +static PyObject * +psutil_proc_memory_info(PyObject *self, PyObject *args) +{ + HANDLE hProcess; + DWORD pid; +#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 + PROCESS_MEMORY_COUNTERS_EX cnt; +#else + PROCESS_MEMORY_COUNTERS cnt; +#endif + SIZE_T private = 0; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid); + if (NULL == hProcess) + return NULL; + + if (! GetProcessMemoryInfo(hProcess, (PPROCESS_MEMORY_COUNTERS)&cnt, + sizeof(cnt))) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(0); + } + +#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 + private = cnt.PrivateUsage; +#endif + + 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)private); +#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)private); +#endif +} + + +/* + * Alternative implementation of the one above but bypasses ACCESS DENIED. + */ +static PyObject * +psutil_proc_memory_info_2(PyObject *self, PyObject *args) +{ + DWORD pid; + PSYSTEM_PROCESS_INFORMATION process; + PVOID buffer; + SIZE_T private; + unsigned long pfault_count; + +#if defined(_WIN64) + unsigned long long m1, m2, m3, m4, m5, m6, m7, m8; +#else + unsigned int m1, m2, m3, m4, m5, m6, m7, m8; +#endif + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_get_proc_info(pid, &process, &buffer)) + return NULL; + +#if (_WIN32_WINNT >= 0x0501) // Windows XP with SP2 + private = process->PrivatePageCount; +#else + private = 0; +#endif + pfault_count = process->PageFaultCount; + + m1 = process->PeakWorkingSetSize; + m2 = process->WorkingSetSize; + m3 = process->QuotaPeakPagedPoolUsage; + m4 = process->QuotaPagedPoolUsage; + m5 = process->QuotaPeakNonPagedPoolUsage; + m6 = process->QuotaNonPagedPoolUsage; + m7 = process->PagefileUsage; + m8 = process->PeakPagefileUsage; + + free(buffer); + + // SYSTEM_PROCESS_INFORMATION values are defined as SIZE_T which on 64 + // bits 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)", +#else + return Py_BuildValue("(kIIIIIIIII)", +#endif + pfault_count, m1, m2, m3, m4, m5, m6, m7, m8, private); +} + + +/* + * 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)) + return PyErr_SetFromWindowsErr(0); + 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 +} + + +/* + * Retrieves system CPU timing information as a (user, system, idle) + * tuple. On a multiprocessor system, the values returned are the + * sum of the designated times across all processors. + */ +static PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) +{ + float idle, kernel, user, system; + FILETIME idle_time, kernel_time, user_time; + + if (!GetSystemTimes(&idle_time, &kernel_time, &user_time)) + return PyErr_SetFromWindowsErr(0); + + idle = (float)((HI_T * idle_time.dwHighDateTime) + \ + (LO_T * idle_time.dwLowDateTime)); + user = (float)((HI_T * user_time.dwHighDateTime) + \ + (LO_T * user_time.dwLowDateTime)); + kernel = (float)((HI_T * kernel_time.dwHighDateTime) + \ + (LO_T * kernel_time.dwLowDateTime)); + + // Kernel time includes idle time. + // We return only busy kernel time subtracting idle time from + // kernel time. + system = (kernel - idle); + return Py_BuildValue("(fff)", user, system, idle); +} + + +/* + * Same as above but for all system CPUs. + */ +static PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) +{ + float idle, kernel, user; + typedef DWORD (_stdcall * NTQSI_PROC) (int, PVOID, ULONG, PULONG); + NTQSI_PROC NtQuerySystemInformation; + HINSTANCE hNtDll; + SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *sppi = NULL; + SYSTEM_INFO si; + UINT i; + PyObject *arg = NULL; + PyObject *retlist = PyList_New(0); + + if (retlist == NULL) + return NULL; + + // dynamic linking is mandatory to use NtQuerySystemInformation + hNtDll = LoadLibrary(TEXT("ntdll.dll")); + if (hNtDll != NULL) { + // gets NtQuerySystemInformation address + NtQuerySystemInformation = (NTQSI_PROC)GetProcAddress( + hNtDll, "NtQuerySystemInformation"); + + if (NtQuerySystemInformation != NULL) + { + // retrives number of processors + GetSystemInfo(&si); + + // allocates an array of SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION + // structures, one per processor + sppi = (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION *) \ + malloc(si.dwNumberOfProcessors * \ + sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION)); + if (sppi != NULL) + { + // gets cpu time informations + if (0 == NtQuerySystemInformation( + SystemProcessorPerformanceInformation, + sppi, + si.dwNumberOfProcessors * sizeof + (SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION), + NULL) + ) + { + // computes system global times summing each + // processor value + idle = user = kernel = 0; + for (i = 0; i < si.dwNumberOfProcessors; i++) { + arg = NULL; + user = (float)((HI_T * sppi[i].UserTime.HighPart) + + (LO_T * sppi[i].UserTime.LowPart)); + idle = (float)((HI_T * sppi[i].IdleTime.HighPart) + + (LO_T * sppi[i].IdleTime.LowPart)); + kernel = (float)((HI_T * sppi[i].KernelTime.HighPart) + + (LO_T * sppi[i].KernelTime.LowPart)); + // kernel time includes idle time on windows + // we return only busy kernel time subtracting + // idle time from kernel time + arg = Py_BuildValue("(ddd)", + user, + kernel - idle, + idle); + if (!arg) + goto error; + if (PyList_Append(retlist, arg)) + goto error; + Py_DECREF(arg); + } + free(sppi); + FreeLibrary(hNtDll); + return retlist; + + } // END NtQuerySystemInformation + } // END malloc SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION + } // END GetProcAddress + } // END LoadLibrary + goto error; + +error: + Py_XDECREF(arg); + Py_DECREF(retlist); + if (sppi) + free(sppi); + if (hNtDll) + FreeLibrary(hNtDll); + PyErr_SetFromWindowsErr(0); + return NULL; +} + + +/* + * Return process current working directory as a Python string. + */ + +static PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) +{ + long pid; + HANDLE processHandle = NULL; + PVOID pebAddress; + PVOID rtlUserProcParamsAddress; + UNICODE_STRING currentDirectory; + WCHAR *currentDirectoryContent = NULL; + PyObject *returnPyObj = NULL; + PyObject *cwd_from_wchar = NULL; + PyObject *cwd = NULL; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + processHandle = psutil_handle_from_pid(pid); + if (processHandle == NULL) + return NULL; + + pebAddress = psutil_get_peb_address(processHandle); + + // get the address of ProcessParameters +#ifdef _WIN64 + if (!ReadProcessMemory(processHandle, (PCHAR)pebAddress + 32, + &rtlUserProcParamsAddress, sizeof(PVOID), NULL)) +#else + if (!ReadProcessMemory(processHandle, (PCHAR)pebAddress + 0x10, + &rtlUserProcParamsAddress, sizeof(PVOID), NULL)) +#endif + { + CloseHandle(processHandle); + if (GetLastError() == ERROR_PARTIAL_COPY) { + // this occurs quite often with system processes + return AccessDenied(); + } + else { + return PyErr_SetFromWindowsErr(0); + } + } + + // Read the currentDirectory UNICODE_STRING structure. + // 0x24 refers to "CurrentDirectoryPath" of RTL_USER_PROCESS_PARAMETERS + // structure, see: + // http://wj32.wordpress.com/2009/01/24/ + // howto-get-the-command-line-of-processes/ +#ifdef _WIN64 + if (!ReadProcessMemory(processHandle, (PCHAR)rtlUserProcParamsAddress + 56, + ¤tDirectory, sizeof(currentDirectory), NULL)) +#else + if (!ReadProcessMemory(processHandle, + (PCHAR)rtlUserProcParamsAddress + 0x24, + ¤tDirectory, sizeof(currentDirectory), NULL)) +#endif + { + CloseHandle(processHandle); + if (GetLastError() == ERROR_PARTIAL_COPY) { + // this occurs quite often with system processes + return AccessDenied(); + } + else { + return PyErr_SetFromWindowsErr(0); + } + } + + // allocate memory to hold cwd + currentDirectoryContent = (WCHAR *)malloc(currentDirectory.Length + 1); + if (currentDirectoryContent == NULL) { + PyErr_NoMemory(); + goto error; + } + + // read cwd + if (!ReadProcessMemory(processHandle, currentDirectory.Buffer, + currentDirectoryContent, currentDirectory.Length, + NULL)) + { + if (GetLastError() == ERROR_PARTIAL_COPY) { + // this occurs quite often with system processes + AccessDenied(); + } + else { + PyErr_SetFromWindowsErr(0); + } + goto error; + } + + // null-terminate the string to prevent wcslen from returning + // incorrect length the length specifier is in characters, but + // currentDirectory.Length is in bytes + currentDirectoryContent[(currentDirectory.Length / sizeof(WCHAR))] = '\0'; + + // convert wchar array to a Python unicode string, and then to UTF8 + cwd_from_wchar = PyUnicode_FromWideChar(currentDirectoryContent, + wcslen(currentDirectoryContent)); + if (cwd_from_wchar == NULL) + goto error; + +#if PY_MAJOR_VERSION >= 3 + cwd = PyUnicode_FromObject(cwd_from_wchar); +#else + cwd = PyUnicode_AsUTF8String(cwd_from_wchar); +#endif + if (cwd == NULL) + goto error; + + // decrement the reference count on our temp unicode str to avoid + // mem leak + returnPyObj = Py_BuildValue("N", cwd); + if (!returnPyObj) + goto error; + + Py_DECREF(cwd_from_wchar); + + CloseHandle(processHandle); + free(currentDirectoryContent); + return returnPyObj; + +error: + Py_XDECREF(cwd_from_wchar); + Py_XDECREF(cwd); + Py_XDECREF(returnPyObj); + if (currentDirectoryContent != NULL) + free(currentDirectoryContent); + if (processHandle != NULL) + CloseHandle(processHandle); + return NULL; +} + + +/* + * Resume or suspends a process + */ +int +psutil_proc_suspend_or_resume(DWORD pid, int suspend) +{ + // a huge thanks to http://www.codeproject.com/KB/threads/pausep.aspx + HANDLE hThreadSnap = NULL; + THREADENTRY32 te32 = {0}; + + if (pid == 0) { + AccessDenied(); + return FALSE; + } + + hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hThreadSnap == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + return FALSE; + } + + // Fill in the size of the structure before using it + te32.dwSize = sizeof(THREADENTRY32); + + if (! Thread32First(hThreadSnap, &te32)) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hThreadSnap); + return FALSE; + } + + // Walk the thread snapshot to find all threads of the process. + // If the thread belongs to the process, add its information + // to the display list. + do + { + if (te32.th32OwnerProcessID == pid) + { + HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, + te32.th32ThreadID); + if (hThread == NULL) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hThread); + CloseHandle(hThreadSnap); + return FALSE; + } + if (suspend == 1) + { + if (SuspendThread(hThread) == (DWORD) - 1) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hThread); + CloseHandle(hThreadSnap); + return FALSE; + } + } + else + { + if (ResumeThread(hThread) == (DWORD) - 1) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hThread); + CloseHandle(hThreadSnap); + return FALSE; + } + } + CloseHandle(hThread); + } + } while (Thread32Next(hThreadSnap, &te32)); + + CloseHandle(hThreadSnap); + return TRUE; +} + + +static PyObject * +psutil_proc_suspend(PyObject *self, PyObject *args) +{ + long pid; + int suspend = 1; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_proc_suspend_or_resume(pid, suspend)) + return NULL; + Py_RETURN_NONE; +} + + +static PyObject * +psutil_proc_resume(PyObject *self, PyObject *args) +{ + long pid; + int suspend = 0; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_proc_suspend_or_resume(pid, suspend)) + return NULL; + Py_RETURN_NONE; +} + + +static PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) +{ + HANDLE hThread; + THREADENTRY32 te32 = {0}; + long pid; + int pid_return; + int rc; + FILETIME ftDummy, ftKernel, ftUser; + PyObject *retList = PyList_New(0); + PyObject *pyTuple = NULL; + HANDLE hThreadSnap = NULL; + + if (retList == NULL) + return NULL; + if (! PyArg_ParseTuple(args, "l", &pid)) + goto error; + if (pid == 0) { + // raise AD instead of returning 0 as procexp is able to + // retrieve useful information somehow + AccessDenied(); + goto error; + } + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) { + NoSuchProcess(); + goto error; + } + if (pid_return == -1) + goto error; + + hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hThreadSnap == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + goto error; + } + + // Fill in the size of the structure before using it + te32.dwSize = sizeof(THREADENTRY32); + + if (! Thread32First(hThreadSnap, &te32)) { + PyErr_SetFromWindowsErr(0); + 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) { + pyTuple = 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_SetFromWindowsErr(0); + goto error; + } + + /* + * User and kernel times are represented as a FILETIME structure + * wich 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 + */ + pyTuple = Py_BuildValue( + "kdd", + te32.th32ThreadID, + (double)(ftUser.dwHighDateTime * 429.4967296 + \ + ftUser.dwLowDateTime * 1e-7), + (double)(ftKernel.dwHighDateTime * 429.4967296 + \ + ftKernel.dwLowDateTime * 1e-7)); + if (!pyTuple) + goto error; + if (PyList_Append(retList, pyTuple)) + goto error; + Py_DECREF(pyTuple); + + CloseHandle(hThread); + } + } while (Thread32Next(hThreadSnap, &te32)); + + CloseHandle(hThreadSnap); + return retList; + +error: + Py_XDECREF(pyTuple); + Py_DECREF(retList); + if (hThread != NULL) + CloseHandle(hThread); + if (hThreadSnap != NULL) + CloseHandle(hThreadSnap); + return NULL; +} + + +static PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) +{ + long pid; + HANDLE processHandle; + DWORD access = PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION; + PyObject *filesList; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + processHandle = psutil_handle_from_pid_waccess(pid, access); + if (processHandle == NULL) + return NULL; + filesList = psutil_get_open_files(pid, processHandle); + CloseHandle(processHandle); + if (filesList == NULL) + return PyErr_SetFromWindowsErr(0); + return filesList; +} + + +/* + Accept a filename's drive in native format like "\Device\HarddiskVolume1\" + and return the corresponding drive letter (e.g. "C:\\"). + If no match is found return an empty string. +*/ +static PyObject * +psutil_win32_QueryDosDevice(PyObject *self, PyObject *args) +{ + LPCTSTR lpDevicePath; + TCHAR d = TEXT('A'); + TCHAR szBuff[5]; + + if (!PyArg_ParseTuple(args, "s", &lpDevicePath)) + return NULL; + + while (d <= TEXT('Z')) { + TCHAR szDeviceName[3] = {d, TEXT(':'), TEXT('\0')}; + TCHAR szTarget[512] = {0}; + if (QueryDosDevice(szDeviceName, szTarget, 511) != 0) { + if (_tcscmp(lpDevicePath, szTarget) == 0) { + _stprintf_s(szBuff, _countof(szBuff), TEXT("%c:"), d); + return Py_BuildValue("s", szBuff); + } + } + d++; + } + return Py_BuildValue("s", ""); +} + + +/* + * Return process username as a "DOMAIN//USERNAME" string. + */ +static PyObject * +psutil_proc_username(PyObject *self, PyObject *args) +{ + long pid; + HANDLE processHandle; + HANDLE tokenHandle; + PTOKEN_USER user; + ULONG bufferSize; + PTSTR name; + ULONG nameSize; + PTSTR domainName; + ULONG domainNameSize; + SID_NAME_USE nameUse; + PTSTR fullName; + PyObject *returnObject; + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + processHandle = psutil_handle_from_pid_waccess( + pid, PROCESS_QUERY_INFORMATION); + if (processHandle == NULL) + return NULL; + + if (!OpenProcessToken(processHandle, TOKEN_QUERY, &tokenHandle)) { + CloseHandle(processHandle); + return PyErr_SetFromWindowsErr(0); + } + + CloseHandle(processHandle); + + // Get the user SID. + + bufferSize = 0x100; + user = malloc(bufferSize); + if (user == NULL) + return PyErr_NoMemory(); + + if (!GetTokenInformation(tokenHandle, TokenUser, user, bufferSize, + &bufferSize)) + { + free(user); + user = malloc(bufferSize); + if (user == NULL) { + CloseHandle(tokenHandle); + return PyErr_NoMemory(); + } + if (!GetTokenInformation(tokenHandle, TokenUser, user, bufferSize, + &bufferSize)) + { + free(user); + CloseHandle(tokenHandle); + return PyErr_SetFromWindowsErr(0); + } + } + + CloseHandle(tokenHandle); + + // resolve the SID to a name + nameSize = 0x100; + domainNameSize = 0x100; + + name = malloc(nameSize * sizeof(TCHAR)); + if (name == NULL) + return PyErr_NoMemory(); + domainName = malloc(domainNameSize * sizeof(TCHAR)); + if (domainName == NULL) + return PyErr_NoMemory(); + + if (!LookupAccountSid(NULL, user->User.Sid, name, &nameSize, domainName, + &domainNameSize, &nameUse)) + { + free(name); + free(domainName); + name = malloc(nameSize * sizeof(TCHAR)); + if (name == NULL) + return PyErr_NoMemory(); + domainName = malloc(domainNameSize * sizeof(TCHAR)); + if (domainName == NULL) + return PyErr_NoMemory(); + if (!LookupAccountSid(NULL, user->User.Sid, name, &nameSize, + domainName, &domainNameSize, &nameUse)) + { + free(name); + free(domainName); + free(user); + + return PyErr_SetFromWindowsErr(0); + } + } + + nameSize = _tcslen(name); + domainNameSize = _tcslen(domainName); + + // build the full username string + fullName = malloc((domainNameSize + 1 + nameSize + 1) * sizeof(TCHAR)); + if (fullName == NULL) { + free(name); + free(domainName); + free(user); + return PyErr_NoMemory(); + } + memcpy(fullName, domainName, domainNameSize); + fullName[domainNameSize] = '\\'; + memcpy(&fullName[domainNameSize + 1], name, nameSize); + fullName[domainNameSize + 1 + nameSize] = '\0'; + + returnObject = PyUnicode_Decode( + fullName, _tcslen(fullName), Py_FileSystemDefaultEncoding, "replace"); + + free(fullName); + free(name); + free(domainName); + free(user); + + return returnObject; +} + + +/* + * Return a list of network connections opened by a process + */ +static PyObject * +psutil_net_connections(PyObject *self, PyObject *args) +{ + static long null_address[4] = { 0, 0, 0, 0 }; + + unsigned long pid; + PyObject *connectionsList; + PyObject *connectionTuple = NULL; + PyObject *af_filter = NULL; + PyObject *type_filter = NULL; + + PyObject *_AF_INET = PyLong_FromLong((long)AF_INET); + PyObject *_AF_INET6 = PyLong_FromLong((long)AF_INET6); + PyObject *_SOCK_STREAM = PyLong_FromLong((long)SOCK_STREAM); + PyObject *_SOCK_DGRAM = PyLong_FromLong((long)SOCK_DGRAM); + + typedef PSTR (NTAPI * _RtlIpv4AddressToStringA)(struct in_addr *, PSTR); + _RtlIpv4AddressToStringA rtlIpv4AddressToStringA; + typedef PSTR (NTAPI * _RtlIpv6AddressToStringA)(struct in6_addr *, PSTR); + _RtlIpv6AddressToStringA rtlIpv6AddressToStringA; + typedef DWORD (WINAPI * _GetExtendedTcpTable)(PVOID, PDWORD, BOOL, ULONG, + TCP_TABLE_CLASS, ULONG); + _GetExtendedTcpTable getExtendedTcpTable; + typedef DWORD (WINAPI * _GetExtendedUdpTable)(PVOID, PDWORD, BOOL, ULONG, + UDP_TABLE_CLASS, ULONG); + _GetExtendedUdpTable getExtendedUdpTable; + PVOID table = NULL; + DWORD tableSize; + PMIB_TCPTABLE_OWNER_PID tcp4Table; + PMIB_UDPTABLE_OWNER_PID udp4Table; + PMIB_TCP6TABLE_OWNER_PID tcp6Table; + PMIB_UDP6TABLE_OWNER_PID udp6Table; + ULONG i; + CHAR addressBufferLocal[65]; + PyObject *addressTupleLocal = NULL; + CHAR addressBufferRemote[65]; + PyObject *addressTupleRemote = NULL; + + if (! PyArg_ParseTuple(args, "lOO", &pid, &af_filter, &type_filter)) { + _psutil_conn_decref_objs(); + return NULL; + } + + if (!PySequence_Check(af_filter) || !PySequence_Check(type_filter)) { + _psutil_conn_decref_objs(); + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + return NULL; + } + + if (pid != -1) { + if (psutil_pid_is_running(pid) == 0) { + _psutil_conn_decref_objs(); + return NoSuchProcess(); + } + } + + // Import some functions. + { + HMODULE ntdll; + HMODULE iphlpapi; + + ntdll = LoadLibrary(TEXT("ntdll.dll")); + rtlIpv4AddressToStringA = (_RtlIpv4AddressToStringA)GetProcAddress( + ntdll, "RtlIpv4AddressToStringA"); + rtlIpv6AddressToStringA = (_RtlIpv6AddressToStringA)GetProcAddress( + ntdll, "RtlIpv6AddressToStringA"); + /* TODO: Check these two function pointers */ + + iphlpapi = LoadLibrary(TEXT("iphlpapi.dll")); + getExtendedTcpTable = (_GetExtendedTcpTable)GetProcAddress(iphlpapi, + "GetExtendedTcpTable"); + getExtendedUdpTable = (_GetExtendedUdpTable)GetProcAddress(iphlpapi, + "GetExtendedUdpTable"); + FreeLibrary(ntdll); + FreeLibrary(iphlpapi); + } + + if ((getExtendedTcpTable == NULL) || (getExtendedUdpTable == NULL)) { + PyErr_SetString(PyExc_NotImplementedError, + "feature not supported on this Windows version"); + _psutil_conn_decref_objs(); + return NULL; + } + + connectionsList = PyList_New(0); + if (connectionsList == NULL) { + _psutil_conn_decref_objs(); + return NULL; + } + + // TCP IPv4 + + if ((PySequence_Contains(af_filter, _AF_INET) == 1) && + (PySequence_Contains(type_filter, _SOCK_STREAM) == 1)) + { + table = NULL; + connectionTuple = NULL; + addressTupleLocal = NULL; + addressTupleRemote = NULL; + tableSize = 0; + getExtendedTcpTable(NULL, &tableSize, FALSE, AF_INET, + TCP_TABLE_OWNER_PID_ALL, 0); + + table = malloc(tableSize); + if (table == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (getExtendedTcpTable(table, &tableSize, FALSE, AF_INET, + TCP_TABLE_OWNER_PID_ALL, 0) == 0) + { + tcp4Table = table; + + for (i = 0; i < tcp4Table->dwNumEntries; i++) + { + if (pid != -1) { + if (tcp4Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (tcp4Table->table[i].dwLocalAddr != 0 || + tcp4Table->table[i].dwLocalPort != 0) + { + struct in_addr addr; + + addr.S_un.S_addr = tcp4Table->table[i].dwLocalAddr; + rtlIpv4AddressToStringA(&addr, addressBufferLocal); + addressTupleLocal = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(tcp4Table->table[i].dwLocalPort)); + } + else { + addressTupleLocal = PyTuple_New(0); + } + + if (addressTupleLocal == NULL) + goto error; + + // On Windows <= XP, remote addr is filled even if socket + // is in LISTEN mode in which case we just ignore it. + if ((tcp4Table->table[i].dwRemoteAddr != 0 || + tcp4Table->table[i].dwRemotePort != 0) && + (tcp4Table->table[i].dwState != MIB_TCP_STATE_LISTEN)) + { + struct in_addr addr; + + addr.S_un.S_addr = tcp4Table->table[i].dwRemoteAddr; + rtlIpv4AddressToStringA(&addr, addressBufferRemote); + addressTupleRemote = Py_BuildValue( + "(si)", + addressBufferRemote, + BYTESWAP_USHORT(tcp4Table->table[i].dwRemotePort)); + } + else + { + addressTupleRemote = PyTuple_New(0); + } + + if (addressTupleRemote == NULL) + goto error; + + connectionTuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET, + SOCK_STREAM, + addressTupleLocal, + addressTupleRemote, + tcp4Table->table[i].dwState, + tcp4Table->table[i].dwOwningPid); + if (!connectionTuple) + goto error; + if (PyList_Append(connectionsList, connectionTuple)) + goto error; + Py_DECREF(connectionTuple); + } + } + + free(table); + } + + // TCP IPv6 + + if ((PySequence_Contains(af_filter, _AF_INET6) == 1) && + (PySequence_Contains(type_filter, _SOCK_STREAM) == 1)) + { + table = NULL; + connectionTuple = NULL; + addressTupleLocal = NULL; + addressTupleRemote = NULL; + tableSize = 0; + getExtendedTcpTable(NULL, &tableSize, FALSE, AF_INET6, + TCP_TABLE_OWNER_PID_ALL, 0); + + table = malloc(tableSize); + if (table == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (getExtendedTcpTable(table, &tableSize, FALSE, AF_INET6, + TCP_TABLE_OWNER_PID_ALL, 0) == 0) + { + tcp6Table = table; + + for (i = 0; i < tcp6Table->dwNumEntries; i++) + { + if (pid != -1) { + if (tcp6Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (memcmp(tcp6Table->table[i].ucLocalAddr, null_address, 16) + != 0 || tcp6Table->table[i].dwLocalPort != 0) + { + struct in6_addr addr; + + memcpy(&addr, tcp6Table->table[i].ucLocalAddr, 16); + rtlIpv6AddressToStringA(&addr, addressBufferLocal); + addressTupleLocal = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(tcp6Table->table[i].dwLocalPort)); + } + else + { + addressTupleLocal = PyTuple_New(0); + } + + if (addressTupleLocal == NULL) + goto error; + + // On Windows <= XP, remote addr is filled even if socket + // is in LISTEN mode in which case we just ignore it. + if ((memcmp(tcp6Table->table[i].ucRemoteAddr, null_address, 16) + != 0 || + tcp6Table->table[i].dwRemotePort != 0) && + (tcp6Table->table[i].dwState != MIB_TCP_STATE_LISTEN)) + { + struct in6_addr addr; + + memcpy(&addr, tcp6Table->table[i].ucRemoteAddr, 16); + rtlIpv6AddressToStringA(&addr, addressBufferRemote); + addressTupleRemote = Py_BuildValue( + "(si)", + addressBufferRemote, + BYTESWAP_USHORT(tcp6Table->table[i].dwRemotePort)); + } + else + { + addressTupleRemote = PyTuple_New(0); + } + + if (addressTupleRemote == NULL) + goto error; + + connectionTuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET6, + SOCK_STREAM, + addressTupleLocal, + addressTupleRemote, + tcp6Table->table[i].dwState, + tcp6Table->table[i].dwOwningPid); + if (!connectionTuple) + goto error; + if (PyList_Append(connectionsList, connectionTuple)) + goto error; + Py_DECREF(connectionTuple); + } + } + + free(table); + } + + // UDP IPv4 + + if ((PySequence_Contains(af_filter, _AF_INET) == 1) && + (PySequence_Contains(type_filter, _SOCK_DGRAM) == 1)) + { + table = NULL; + connectionTuple = NULL; + addressTupleLocal = NULL; + addressTupleRemote = NULL; + tableSize = 0; + getExtendedUdpTable(NULL, &tableSize, FALSE, AF_INET, + UDP_TABLE_OWNER_PID, 0); + + table = malloc(tableSize); + if (table == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (getExtendedUdpTable(table, &tableSize, FALSE, AF_INET, + UDP_TABLE_OWNER_PID, 0) == 0) + { + udp4Table = table; + + for (i = 0; i < udp4Table->dwNumEntries; i++) + { + if (pid != -1) { + if (udp4Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (udp4Table->table[i].dwLocalAddr != 0 || + udp4Table->table[i].dwLocalPort != 0) + { + struct in_addr addr; + + addr.S_un.S_addr = udp4Table->table[i].dwLocalAddr; + rtlIpv4AddressToStringA(&addr, addressBufferLocal); + addressTupleLocal = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(udp4Table->table[i].dwLocalPort)); + } + else { + addressTupleLocal = PyTuple_New(0); + } + + if (addressTupleLocal == NULL) + goto error; + + connectionTuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET, + SOCK_DGRAM, + addressTupleLocal, + PyTuple_New(0), + PSUTIL_CONN_NONE, + udp4Table->table[i].dwOwningPid); + if (!connectionTuple) + goto error; + if (PyList_Append(connectionsList, connectionTuple)) + goto error; + Py_DECREF(connectionTuple); + } + } + + free(table); + } + + // UDP IPv6 + + if ((PySequence_Contains(af_filter, _AF_INET6) == 1) && + (PySequence_Contains(type_filter, _SOCK_DGRAM) == 1)) + { + table = NULL; + connectionTuple = NULL; + addressTupleLocal = NULL; + addressTupleRemote = NULL; + tableSize = 0; + getExtendedUdpTable(NULL, &tableSize, FALSE, + AF_INET6, UDP_TABLE_OWNER_PID, 0); + + table = malloc(tableSize); + if (table == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (getExtendedUdpTable(table, &tableSize, FALSE, AF_INET6, + UDP_TABLE_OWNER_PID, 0) == 0) + { + udp6Table = table; + + for (i = 0; i < udp6Table->dwNumEntries; i++) + { + if (pid != -1) { + if (udp6Table->table[i].dwOwningPid != pid) { + continue; + } + } + + if (memcmp(udp6Table->table[i].ucLocalAddr, null_address, 16) + != 0 || udp6Table->table[i].dwLocalPort != 0) + { + struct in6_addr addr; + + memcpy(&addr, udp6Table->table[i].ucLocalAddr, 16); + rtlIpv6AddressToStringA(&addr, addressBufferLocal); + addressTupleLocal = Py_BuildValue( + "(si)", + addressBufferLocal, + BYTESWAP_USHORT(udp6Table->table[i].dwLocalPort)); + } + else { + addressTupleLocal = PyTuple_New(0); + } + + if (addressTupleLocal == NULL) + goto error; + + connectionTuple = Py_BuildValue( + "(iiiNNiI)", + -1, + AF_INET6, + SOCK_DGRAM, + addressTupleLocal, + PyTuple_New(0), + PSUTIL_CONN_NONE, + udp6Table->table[i].dwOwningPid); + if (!connectionTuple) + goto error; + if (PyList_Append(connectionsList, connectionTuple)) + goto error; + Py_DECREF(connectionTuple); + } + } + + free(table); + } + + _psutil_conn_decref_objs(); + return connectionsList; + +error: + _psutil_conn_decref_objs(); + Py_XDECREF(connectionTuple); + Py_XDECREF(addressTupleLocal); + Py_XDECREF(addressTupleRemote); + Py_DECREF(connectionsList); + if (table != NULL) + free(table); + return NULL; +} + + +/* + * Get process priority as a Python integer. + */ +static PyObject * +psutil_proc_priority_get(PyObject *self, PyObject *args) +{ + long pid; + DWORD priority; + HANDLE hProcess; + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid); + if (hProcess == NULL) { + return NULL; + } + + priority = GetPriorityClass(hProcess); + CloseHandle(hProcess); + if (priority == 0) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + return Py_BuildValue("i", priority); +} + + +/* + * Set process priority. + */ +static PyObject * +psutil_proc_priority_set(PyObject *self, PyObject *args) +{ + long pid; + int priority; + int retval; + HANDLE hProcess; + DWORD dwDesiredAccess = \ + PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + if (! PyArg_ParseTuple(args, "li", &pid, &priority)) { + return NULL; + } + + hProcess = psutil_handle_from_pid_waccess(pid, dwDesiredAccess); + if (hProcess == NULL) { + return NULL; + } + + retval = SetPriorityClass(hProcess, priority); + CloseHandle(hProcess); + if (retval == 0) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + Py_RETURN_NONE; +} + + +#if (_WIN32_WINNT >= 0x0600) // Windows Vista +/* + * Get process IO priority as a Python integer. + */ +static PyObject * +psutil_proc_io_priority_get(PyObject *self, PyObject *args) +{ + long pid; + HANDLE hProcess; + PULONG IoPriority; + + _NtQueryInformationProcess NtQueryInformationProcess = + (_NtQueryInformationProcess)GetProcAddress( + GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid); + if (hProcess == NULL) { + return NULL; + } + + NtQueryInformationProcess( + hProcess, + ProcessIoPriority, + &IoPriority, + sizeof(ULONG), + NULL + ); + CloseHandle(hProcess); + return Py_BuildValue("i", IoPriority); +} + + +/* + * Set process IO priority. + */ +static PyObject * +psutil_proc_io_priority_set(PyObject *self, PyObject *args) +{ + long pid; + int prio; + HANDLE hProcess; + + _NtSetInformationProcess NtSetInformationProcess = + (_NtSetInformationProcess)GetProcAddress( + GetModuleHandleA("ntdll.dll"), "NtSetInformationProcess"); + + if (NtSetInformationProcess == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "couldn't get NtSetInformationProcess"); + return NULL; + } + + if (! PyArg_ParseTuple(args, "li", &pid, &prio)) { + return NULL; + } + hProcess = psutil_handle_from_pid_waccess(pid, PROCESS_ALL_ACCESS); + if (hProcess == NULL) { + return NULL; + } + + NtSetInformationProcess( + hProcess, + ProcessIoPriority, + (PVOID)&prio, + sizeof((PVOID)prio) + ); + + CloseHandle(hProcess); + Py_RETURN_NONE; +} +#endif + + +/* + * 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, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid); + if (NULL == hProcess) { + return NULL; + } + if (! GetProcessIoCounters(hProcess, &IoCounters)) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(0); + } + CloseHandle(hProcess); + return Py_BuildValue("(KKKK)", + IoCounters.ReadOperationCount, + IoCounters.WriteOperationCount, + IoCounters.ReadTransferCount, + IoCounters.WriteTransferCount); +} + + +/* + * 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, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid); + if (hProcess == NULL) { + return NULL; + } + if (GetProcessAffinityMask(hProcess, &proc_mask, &system_mask) == 0) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(0); + } + + 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 dwDesiredAccess = \ + PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + DWORD_PTR mask; + +#ifdef _WIN64 + if (! PyArg_ParseTuple(args, "lK", &pid, &mask)) +#else + if (! PyArg_ParseTuple(args, "lk", &pid, &mask)) +#endif + { + return NULL; + } + hProcess = psutil_handle_from_pid_waccess(pid, dwDesiredAccess); + if (hProcess == NULL) { + return NULL; + } + + if (SetProcessAffinityMask(hProcess, mask) == 0) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(0); + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +/* + * Return True if one of the process threads is in a waiting or + * suspended status. + */ +static PyObject * +psutil_proc_is_suspended(PyObject *self, PyObject *args) +{ + DWORD pid; + ULONG i; + PSYSTEM_PROCESS_INFORMATION process; + PVOID buffer; + + if (! PyArg_ParseTuple(args, "l", &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 path's disk total and free as a Python tuple. + */ +static PyObject * +psutil_disk_usage(PyObject *self, PyObject *args) +{ + BOOL retval; + ULARGE_INTEGER _, total, free; + char *path; + + if (PyArg_ParseTuple(args, "u", &path)) { + Py_BEGIN_ALLOW_THREADS + retval = GetDiskFreeSpaceExW((LPCWSTR)path, &_, &total, &free); + Py_END_ALLOW_THREADS + goto return_; + } + + // 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 + retval = GetDiskFreeSpaceEx(path, &_, &total, &free); + Py_END_ALLOW_THREADS + goto return_; + } +#endif + + return NULL; + +return_: + if (retval == 0) + return PyErr_SetFromWindowsErr(0); + else + return Py_BuildValue("(LL)", total.QuadPart, free.QuadPart); +} + + +/* + * Return a Python list of named tuples with overall network I/O information + */ +static PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) +{ + char ifname[MAX_PATH]; + DWORD dwRetVal = 0; + MIB_IFROW *pIfRow = NULL; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_nic_info = NULL; + PyObject *py_nic_name = NULL; + + if (py_retdict == NULL) + return NULL; + pAddresses = psutil_get_nic_addresses(); + if (pAddresses == NULL) + goto error; + pCurrAddresses = pAddresses; + + while (pCurrAddresses) { + py_nic_name = NULL; + py_nic_info = NULL; + pIfRow = (MIB_IFROW *) malloc(sizeof(MIB_IFROW)); + + if (pIfRow == NULL) { + PyErr_NoMemory(); + goto error; + } + + pIfRow->dwIndex = pCurrAddresses->IfIndex; + dwRetVal = GetIfEntry(pIfRow); + if (dwRetVal != NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "GetIfEntry() failed."); + goto error; + } + + py_nic_info = Py_BuildValue("(kkkkkkkk)", + pIfRow->dwOutOctets, + pIfRow->dwInOctets, + pIfRow->dwOutUcastPkts, + pIfRow->dwInUcastPkts, + pIfRow->dwInErrors, + pIfRow->dwOutErrors, + pIfRow->dwInDiscards, + pIfRow->dwOutDiscards); + if (!py_nic_info) + goto error; + + sprintf_s(ifname, MAX_PATH, "%wS", pCurrAddresses->FriendlyName); + py_nic_name = PyUnicode_Decode( + ifname, _tcslen(ifname), Py_FileSystemDefaultEncoding, "replace"); + + if (py_nic_name == NULL) + goto error; + if (PyDict_SetItem(py_retdict, py_nic_name, py_nic_info)) + goto error; + Py_XDECREF(py_nic_name); + Py_XDECREF(py_nic_info); + + free(pIfRow); + pCurrAddresses = pCurrAddresses->Next; + } + + free(pAddresses); + return py_retdict; + +error: + Py_XDECREF(py_nic_name); + Py_XDECREF(py_nic_info); + Py_DECREF(py_retdict); + if (pAddresses != NULL) + free(pAddresses); + if (pIfRow != NULL) + free(pIfRow); + return NULL; +} + + +/* + * Return a Python dict of tuples for disk I/O information + */ +static PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) +{ + DISK_PERFORMANCE_WIN_2008 diskPerformance; + DWORD dwSize; + HANDLE hDevice = NULL; + char szDevice[MAX_PATH]; + char szDeviceDisplay[MAX_PATH]; + int devNum; + PyObject *py_retdict = PyDict_New(); + PyObject *py_disk_info = NULL; + if (py_retdict == NULL) { + return NULL; + } + + // Apparently there's no way to figure out how many times we have + // to iterate in order to find valid drives. + // Let's assume 32, which is higher than 26, the number of letters + // in the alphabet (from A:\ to Z:\). + for (devNum = 0; devNum <= 32; ++devNum) { + py_disk_info = NULL; + sprintf_s(szDevice, MAX_PATH, "\\\\.\\PhysicalDrive%d", devNum); + hDevice = CreateFile(szDevice, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + + if (hDevice == INVALID_HANDLE_VALUE) { + continue; + } + if (DeviceIoControl(hDevice, IOCTL_DISK_PERFORMANCE, NULL, 0, + &diskPerformance, sizeof(diskPerformance), + &dwSize, NULL)) + { + sprintf_s(szDeviceDisplay, MAX_PATH, "PhysicalDrive%d", devNum); + py_disk_info = Py_BuildValue( + "(IILLKK)", + diskPerformance.ReadCount, + diskPerformance.WriteCount, + diskPerformance.BytesRead, + diskPerformance.BytesWritten, + (unsigned long long)(diskPerformance.ReadTime.QuadPart * 10) / 1000, + (unsigned long long)(diskPerformance.WriteTime.QuadPart * 10) / 1000); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, szDeviceDisplay, + py_disk_info)) + { + goto error; + } + Py_XDECREF(py_disk_info); + } + else { + // XXX we might get here with ERROR_INSUFFICIENT_BUFFER when + // compiling with mingw32; not sure what to do. + // return PyErr_SetFromWindowsErr(0); + ;; + } + + CloseHandle(hDevice); + } + + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + if (hDevice != NULL) + CloseHandle(hDevice); + return NULL; +} + + +static char *psutil_get_drive_type(int type) +{ + switch (type) { + case DRIVE_FIXED: + return "fixed"; + case DRIVE_CDROM: + return "cdrom"; + case DRIVE_REMOVABLE: + return "removable"; + case DRIVE_UNKNOWN: + return "unknown"; + case DRIVE_NO_ROOT_DIR: + return "unmounted"; + case DRIVE_REMOTE: + return "remote"; + case DRIVE_RAMDISK: + return "ramdisk"; + default: + return "?"; + } +} + + +#ifndef _ARRAYSIZE +#define _ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) +#endif + +/* + * Return disk partitions as a list of tuples such as + * (drive_letter, drive_letter, type, "") + */ +static PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) +{ + DWORD num_bytes; + char drive_strings[255]; + char *drive_letter = drive_strings; + int all; + int type; + int ret; + char opts[20]; + LPTSTR fs_type[MAX_PATH + 1] = { 0 }; + DWORD pflags = 0; + PyObject *py_all; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_retlist == NULL) { + return NULL; + } + + // avoid to visualize a message box in case something goes wrong + // see https://github.com/giampaolo/psutil/issues/264 + SetErrorMode(SEM_FAILCRITICALERRORS); + + if (! PyArg_ParseTuple(args, "O", &py_all)) { + goto error; + } + all = PyObject_IsTrue(py_all); + + Py_BEGIN_ALLOW_THREADS + num_bytes = GetLogicalDriveStrings(254, drive_letter); + Py_END_ALLOW_THREADS + + if (num_bytes == 0) { + PyErr_SetFromWindowsErr(0); + goto error; + } + + while (*drive_letter != 0) { + py_tuple = NULL; + opts[0] = 0; + fs_type[0] = 0; + + Py_BEGIN_ALLOW_THREADS + type = GetDriveType(drive_letter); + Py_END_ALLOW_THREADS + + // by default we only show hard drives and cd-roms + if (all == 0) { + if ((type == DRIVE_UNKNOWN) || + (type == DRIVE_NO_ROOT_DIR) || + (type == DRIVE_REMOTE) || + (type == DRIVE_RAMDISK)) { + goto next; + } + // floppy disk: skip it by default as it introduces a + // considerable slowdown. + if ((type == DRIVE_REMOVABLE) && + (strcmp(drive_letter, "A:\\") == 0)) { + goto next; + } + } + + ret = GetVolumeInformation( + (LPCTSTR)drive_letter, NULL, _ARRAYSIZE(drive_letter), + NULL, NULL, &pflags, (LPTSTR)fs_type, _ARRAYSIZE(fs_type)); + if (ret == 0) { + // We might get here in case of a floppy hard drive, in + // which case the error is (21, "device not ready"). + // Let's pretend it didn't happen as we already have + // the drive name and type ('removable'). + strcat_s(opts, _countof(opts), ""); + SetLastError(0); + } + else { + if (pflags & FILE_READ_ONLY_VOLUME) { + strcat_s(opts, _countof(opts), "ro"); + } + else { + strcat_s(opts, _countof(opts), "rw"); + } + if (pflags & FILE_VOLUME_IS_COMPRESSED) { + strcat_s(opts, _countof(opts), ",compressed"); + } + } + + if (strlen(opts) > 0) { + strcat_s(opts, _countof(opts), ","); + } + strcat_s(opts, _countof(opts), psutil_get_drive_type(type)); + + py_tuple = Py_BuildValue( + "(ssss)", + drive_letter, + drive_letter, + fs_type, // either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS + opts); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + goto next; + +next: + drive_letter = strchr(drive_letter, 0) + 1; + } + + SetErrorMode(0); + return py_retlist; + +error: + SetErrorMode(0); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} + +/* + * Return a Python dict of tuples for disk I/O information + */ +static PyObject * +psutil_users(PyObject *self, PyObject *args) +{ + HANDLE hServer = NULL; + LPTSTR buffer_user = NULL; + LPTSTR buffer_addr = NULL; + PWTS_SESSION_INFO sessions = NULL; + DWORD count; + DWORD i; + DWORD sessionId; + DWORD bytes; + PWTS_CLIENT_ADDRESS address; + char address_str[50]; + long long unix_time; + + PWINSTATIONQUERYINFORMATIONW WinStationQueryInformationW; + WINSTATION_INFO station_info; + HINSTANCE hInstWinSta = NULL; + ULONG returnLen; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_buffer_user_encoded = NULL; + + if (py_retlist == NULL) { + return NULL; + } + + hInstWinSta = LoadLibraryA("winsta.dll"); + WinStationQueryInformationW = (PWINSTATIONQUERYINFORMATIONW) \ + GetProcAddress(hInstWinSta, "WinStationQueryInformationW"); + + hServer = WTSOpenServer('\0'); + if (hServer == NULL) { + PyErr_SetFromWindowsErr(0); + goto error; + } + + if (WTSEnumerateSessions(hServer, 0, 1, &sessions, &count) == 0) { + PyErr_SetFromWindowsErr(0); + 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); + } + + buffer_user = NULL; + buffer_addr = NULL; + + // username + bytes = 0; + if (WTSQuerySessionInformation(hServer, sessionId, WTSUserName, + &buffer_user, &bytes) == 0) { + PyErr_SetFromWindowsErr(0); + goto error; + } + if (bytes == 1) { + continue; + } + + // address + bytes = 0; + if (WTSQuerySessionInformation(hServer, sessionId, WTSClientAddress, + &buffer_addr, &bytes) == 0) { + PyErr_SetFromWindowsErr(0); + goto error; + } + + address = (PWTS_CLIENT_ADDRESS)buffer_addr; + if (address->AddressFamily == 0) { // AF_INET + sprintf_s(address_str, + _countof(address_str), + "%u.%u.%u.%u", + address->Address[0], + address->Address[1], + address->Address[2], + address->Address[3]); + py_address = Py_BuildValue("s", address_str); + if (!py_address) + goto error; + } + else { + py_address = Py_None; + } + + // login time + if (!WinStationQueryInformationW(hServer, + sessionId, + WinStationInformation, + &station_info, + sizeof(station_info), + &returnLen)) + { + goto error; + } + + unix_time = ((LONGLONG)station_info.ConnectTime.dwHighDateTime) << 32; + unix_time += \ + station_info.ConnectTime.dwLowDateTime - 116444736000000000LL; + unix_time /= 10000000; + + py_buffer_user_encoded = PyUnicode_Decode( + buffer_user, _tcslen(buffer_user), Py_FileSystemDefaultEncoding, + "replace"); + py_tuple = Py_BuildValue("OOd", py_buffer_user_encoded, py_address, + (double)unix_time); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_XDECREF(py_buffer_user_encoded); + Py_XDECREF(py_address); + Py_XDECREF(py_tuple); + } + + WTSCloseServer(hServer); + WTSFreeMemory(sessions); + WTSFreeMemory(buffer_user); + WTSFreeMemory(buffer_addr); + FreeLibrary(hInstWinSta); + return py_retlist; + +error: + Py_XDECREF(py_buffer_user_encoded); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_DECREF(py_retlist); + + if (hInstWinSta != NULL) { + FreeLibrary(hInstWinSta); + } + if (hServer != NULL) { + WTSCloseServer(hServer); + } + if (sessions != NULL) { + WTSFreeMemory(sessions); + } + if (buffer_user != NULL) { + WTSFreeMemory(buffer_user); + } + if (buffer_addr != NULL) { + WTSFreeMemory(buffer_addr); + } + 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, "l", &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid); + if (NULL == hProcess) { + return NULL; + } + if (! GetProcessHandleCount(hProcess, &handleCount)) { + CloseHandle(hProcess); + return PyErr_SetFromWindowsErr(0); + } + CloseHandle(hProcess); + return Py_BuildValue("k", handleCount); +} + + +/* + * Get various process information by using NtQuerySystemInformation. + * We use this as a fallback when faster functions fail with access + * denied. This is slower because it iterates over all processes. + * Returned tuple includes the following process info: + * + * - num_threads + * - ctx_switches + * - num_handles (fallback) + * - user/kernel times (fallback) + * - create time (fallback) + * - io counters (fallback) + */ +static PyObject * +psutil_proc_info(PyObject *self, PyObject *args) +{ + DWORD pid; + PSYSTEM_PROCESS_INFORMATION process; + PVOID buffer; + ULONG num_handles; + ULONG i; + ULONG ctx_switches = 0; + double user_time; + double kernel_time; + long long create_time; + int num_threads; + LONGLONG io_rcount, io_wcount, io_rbytes, io_wbytes; + + + if (! PyArg_ParseTuple(args, "l", &pid)) + return NULL; + if (! psutil_get_proc_info(pid, &process, &buffer)) + return NULL; + + num_handles = process->HandleCount; + for (i = 0; i < process->NumberOfThreads; i++) + ctx_switches += process->Threads[i].ContextSwitches; + user_time = (double)process->UserTime.HighPart * 429.4967296 + \ + (double)process->UserTime.LowPart * 1e-7; + kernel_time = (double)process->KernelTime.HighPart * 429.4967296 + \ + (double)process->KernelTime.LowPart * 1e-7; + // Convert the LARGE_INTEGER union to a Unix time. + // It's the best I could find by googling and borrowing code here + // and there. The time returned has a precision of 1 second. + if (0 == pid || 4 == pid) { + // the python module will translate this into BOOT_TIME later + create_time = 0; + } + else { + create_time = ((LONGLONG)process->CreateTime.HighPart) << 32; + create_time += process->CreateTime.LowPart - 116444736000000000LL; + create_time /= 10000000; + } + num_threads = (int)process->NumberOfThreads; + io_rcount = process->ReadOperationCount.QuadPart; + io_wcount = process->WriteOperationCount.QuadPart; + io_rbytes = process->ReadTransferCount.QuadPart; + io_wbytes = process->WriteTransferCount.QuadPart; + free(buffer); + + return Py_BuildValue( + "kkdddiKKKK", + num_handles, + ctx_switches, + user_time, + kernel_time, + (double)create_time, + num_threads, + io_rcount, + io_wcount, + io_rbytes, + io_wbytes + ); +} + + +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) +{ + DWORD pid; + HANDLE hProcess = NULL; + MEMORY_BASIC_INFORMATION basicInfo; + PVOID baseAddress; + PVOID previousAllocationBase; + CHAR mappedFileName[MAX_PATH]; + SYSTEM_INFO system_info; + LPVOID maxAddr; + PyObject *py_list = PyList_New(0); + PyObject *py_tuple = NULL; + + if (py_list == NULL) { + return NULL; + } + if (! PyArg_ParseTuple(args, "l", &pid)) { + goto error; + } + hProcess = psutil_handle_from_pid(pid); + if (NULL == hProcess) { + goto error; + } + + GetSystemInfo(&system_info); + maxAddr = system_info.lpMaximumApplicationAddress; + baseAddress = NULL; + previousAllocationBase = NULL; + + while (VirtualQueryEx(hProcess, baseAddress, &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION))) + { + py_tuple = NULL; + if (baseAddress > maxAddr) { + break; + } + if (GetMappedFileNameA(hProcess, baseAddress, mappedFileName, + sizeof(mappedFileName))) + { + py_tuple = Py_BuildValue( + "(kssI)", + (unsigned long)baseAddress, + get_region_protection_string(basicInfo.Protect), + mappedFileName, + basicInfo.RegionSize); + if (!py_tuple) + goto error; + if (PyList_Append(py_list, py_tuple)) + goto error; + Py_DECREF(py_tuple); + } + previousAllocationBase = basicInfo.AllocationBase; + baseAddress = (PCHAR)baseAddress + basicInfo.RegionSize; + } + + CloseHandle(hProcess); + return py_list; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_list); + 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 *pid = NULL; + PyObject *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 { + pid = Py_BuildValue("I", pe.th32ProcessID); + if (pid == NULL) + goto error; + ppid = Py_BuildValue("I", pe.th32ParentProcessID); + if (ppid == NULL) + goto error; + if (PyDict_SetItem(py_retdict, pid, ppid)) + goto error; + Py_DECREF(pid); + Py_DECREF(ppid); + } while (Process32Next(handle, &pe)); + } + + CloseHandle(handle); + return py_retdict; + +error: + Py_XDECREF(pid); + Py_XDECREF(ppid); + Py_DECREF(py_retdict); + CloseHandle(handle); + return NULL; +} + + +/* + * Return NICs addresses. + */ + +static PyObject * +psutil_net_if_addrs(PyObject *self, PyObject *args) +{ + unsigned int i = 0; + ULONG family; + PCTSTR intRet; + char *ptr; + char buff[100]; + char ifname[MAX_PATH]; + DWORD bufflen = 100; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = NULL; + + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_mac_address = NULL; + + if (py_retlist == NULL) + return NULL; + + pAddresses = psutil_get_nic_addresses(); + if (pAddresses == NULL) + goto error; + pCurrAddresses = pAddresses; + + while (pCurrAddresses) { + pUnicast = pCurrAddresses->FirstUnicastAddress; + sprintf_s(ifname, MAX_PATH, "%wS", pCurrAddresses->FriendlyName); + + // MAC address + if (pCurrAddresses->PhysicalAddressLength != 0) { + ptr = buff; + *ptr = '\0'; + for (i = 0; i < (int) pCurrAddresses->PhysicalAddressLength; i++) { + if (i == (pCurrAddresses->PhysicalAddressLength - 1)) { + sprintf_s(ptr, _countof(buff), "%.2X\n", + (int)pCurrAddresses->PhysicalAddress[i]); + } + else { + sprintf_s(ptr, _countof(buff), "%.2X-", + (int)pCurrAddresses->PhysicalAddress[i]); + } + ptr += 3; + } + *--ptr = '\0'; + +#if PY_MAJOR_VERSION >= 3 + py_mac_address = PyUnicode_FromString(buff); +#else + py_mac_address = PyString_FromString(buff); +#endif + if (py_mac_address == NULL) + goto error; + + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_tuple = Py_BuildValue( + "(siOOO)", + ifname, + -1, // this will be converted later to AF_LINK + py_mac_address, + Py_None, + Py_None + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_mac_address); + } + + // find out the IP address associated with the NIC + if (pUnicast != NULL) { + for (i = 0; pUnicast != NULL; i++) { + family = pUnicast->Address.lpSockaddr->sa_family; + if (family == AF_INET) { + struct sockaddr_in *sa_in = (struct sockaddr_in *) + pUnicast->Address.lpSockaddr; + intRet = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, + bufflen); + } + else if (family == AF_INET6) { + struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *) + pUnicast->Address.lpSockaddr; + intRet = inet_ntop(AF_INET6, &(sa_in6->sin6_addr), + buff, bufflen); + } + else { + // we should never get here + pUnicast = pUnicast->Next; + continue; + } + + if (intRet == NULL) { + PyErr_SetFromWindowsErr(GetLastError()); + goto error; + } +#if PY_MAJOR_VERSION >= 3 + py_address = PyUnicode_FromString(buff); +#else + py_address = PyString_FromString(buff); +#endif + if (py_address == NULL) + goto error; + + Py_INCREF(Py_None); + Py_INCREF(Py_None); + py_tuple = Py_BuildValue( + "(siOOO)", + ifname, + family, + py_address, + Py_None, + Py_None + ); + + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_DECREF(py_tuple); + Py_DECREF(py_address); + + pUnicast = pUnicast->Next; + } + } + + pCurrAddresses = pCurrAddresses->Next; + } + + free(pAddresses); + return py_retlist; + +error: + if (pAddresses) + free(pAddresses); + Py_DECREF(py_retlist); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + return NULL; +} + + +/* + * Provides stats about NIC interfaces installed on the system. + * TODO: get 'duplex' (currently it's hard coded to '2', aka + 'full duplex') + */ +static PyObject * +psutil_net_if_stats(PyObject *self, PyObject *args) +{ + int i; + DWORD dwSize = 0; + DWORD dwRetVal = 0; + MIB_IFTABLE *pIfTable; + MIB_IFROW *pIfRow; + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + char friendly_name[MAX_PATH]; + char descr[MAX_PATH]; + int ifname_found; + + PyObject *py_retdict = PyDict_New(); + PyObject *py_ifc_info = NULL; + PyObject *py_is_up = NULL; + + if (py_retdict == NULL) + return NULL; + + pAddresses = psutil_get_nic_addresses(); + if (pAddresses == NULL) + goto error; + + pIfTable = (MIB_IFTABLE *) malloc(sizeof (MIB_IFTABLE)); + if (pIfTable == NULL) { + PyErr_NoMemory(); + goto error; + } + dwSize = sizeof(MIB_IFTABLE); + if (GetIfTable(pIfTable, &dwSize, FALSE) == ERROR_INSUFFICIENT_BUFFER) { + free(pIfTable); + pIfTable = (MIB_IFTABLE *) malloc(dwSize); + if (pIfTable == NULL) { + PyErr_NoMemory(); + goto error; + } + } + // Make a second call to GetIfTable to get the actual + // data we want. + if ((dwRetVal = GetIfTable(pIfTable, &dwSize, FALSE)) != NO_ERROR) { + PyErr_SetString(PyExc_RuntimeError, "GetIfTable() failed"); + goto error; + } + + for (i = 0; i < (int) pIfTable->dwNumEntries; i++) { + pIfRow = (MIB_IFROW *) & pIfTable->table[i]; + + // GetIfTable is not able to give us NIC with "friendly names" + // so we determine them via GetAdapterAddresses() which + // provides friendly names *and* descriptions and find the + // ones that match. + ifname_found = 0; + pCurrAddresses = pAddresses; + while (pCurrAddresses) { + sprintf_s(descr, MAX_PATH, "%wS", pCurrAddresses->Description); + if (lstrcmp(descr, pIfRow->bDescr) == 0) { + sprintf_s(friendly_name, MAX_PATH, "%wS", pCurrAddresses->FriendlyName); + ifname_found = 1; + break; + } + pCurrAddresses = pCurrAddresses->Next; + } + if (ifname_found == 0) { + // Name not found means GetAdapterAddresses() doesn't list + // this NIC, only GetIfTable, meaning it's not really a NIC + // interface so we skip it. + continue; + } + + // is up? + if((pIfRow->dwOperStatus == MIB_IF_OPER_STATUS_CONNECTED || + pIfRow->dwOperStatus == MIB_IF_OPER_STATUS_OPERATIONAL) && + pIfRow->dwAdminStatus == 1 ) { + py_is_up = Py_True; + } + else { + py_is_up = Py_False; + } + Py_INCREF(py_is_up); + + py_ifc_info = Py_BuildValue( + "(Oikk)", + py_is_up, + 2, // there's no way to know duplex so let's assume 'full' + pIfRow->dwSpeed / 1000000, // expressed in bytes, we want Mb + pIfRow->dwMtu + ); + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, friendly_name, py_ifc_info)) + goto error; + Py_DECREF(py_ifc_info); + } + + free(pIfTable); + free(pAddresses); + return py_retdict; + +error: + Py_XDECREF(py_is_up); + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (pIfTable != NULL) + free(pIfTable); + if (pAddresses != NULL) + free(pAddresses); + return NULL; +} + + +// ------------------------ Python init --------------------------- + +static PyMethodDef +PsutilMethods[] = +{ + // --- per-process functions + + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, + "Return process cmdline as a list of cmdline arguments"}, + {"proc_exe", psutil_proc_exe, METH_VARARGS, + "Return path of the process executable"}, + {"proc_name", psutil_proc_name, METH_VARARGS, + "Return process name"}, + {"proc_kill", psutil_proc_kill, METH_VARARGS, + "Kill the process identified by the given PID"}, + {"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS, + "Return tuple of user/kern time for the given PID"}, + {"proc_create_time", psutil_proc_create_time, METH_VARARGS, + "Return a float indicating the process create time expressed in " + "seconds since the epoch"}, + {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS, + "Return a tuple of process memory information"}, + {"proc_memory_info_2", psutil_proc_memory_info_2, METH_VARARGS, + "Alternate implementation"}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS, + "Return process current working directory"}, + {"proc_suspend", psutil_proc_suspend, METH_VARARGS, + "Suspend a process"}, + {"proc_resume", psutil_proc_resume, METH_VARARGS, + "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."}, +#if (_WIN32_WINNT >= 0x0600) // Windows Vista + {"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."}, +#endif + {"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"}, + + // --- alternative pinfo interface + {"proc_info", psutil_proc_info, METH_VARARGS, + "Various process information"}, + + // --- 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."}, + + // --- windows API bindings + {"win32_QueryDosDevice", psutil_win32_QueryDosDevice, METH_VARARGS, + "QueryDosDevice binding"}, + + {NULL, NULL, 0, NULL} +}; + + +struct module_state { + PyObject *error; +}; + +#if PY_MAJOR_VERSION >= 3 +#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) +#else +#define GETSTATE(m) (&_state) +static struct module_state _state; +#endif + +#if PY_MAJOR_VERSION >= 3 + +static int psutil_windows_traverse(PyObject *m, visitproc visit, void *arg) { + Py_VISIT(GETSTATE(m)->error); + return 0; +} + +static int psutil_windows_clear(PyObject *m) { + Py_CLEAR(GETSTATE(m)->error); + return 0; +} + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "psutil_windows", + NULL, + sizeof(struct module_state), + PsutilMethods, + NULL, + psutil_windows_traverse, + psutil_windows_clear, + NULL +}; + +#define INITERROR return NULL + +PyMODINIT_FUNC PyInit__psutil_windows(void) + +#else +#define INITERROR return +void init_psutil_windows(void) +#endif +{ + struct module_state *st = NULL; +#if PY_MAJOR_VERSION >= 3 + PyObject *module = PyModule_Create(&moduledef); +#else + PyObject *module = Py_InitModule("_psutil_windows", PsutilMethods); +#endif + + if (module == NULL) { + INITERROR; + } + + st = GETSTATE(module); + st->error = PyErr_NewException("_psutil_windows.Error", NULL, NULL); + if (st->error == NULL) { + Py_DECREF(module); + INITERROR; + } + + PyModule_AddIntConstant(module, "version", PSUTIL_VERSION); + + // process status constants + // http://msdn.microsoft.com/en-us/library/ms683211(v=vs.85).aspx + PyModule_AddIntConstant( + module, "ABOVE_NORMAL_PRIORITY_CLASS", ABOVE_NORMAL_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "BELOW_NORMAL_PRIORITY_CLASS", BELOW_NORMAL_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "HIGH_PRIORITY_CLASS", HIGH_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "IDLE_PRIORITY_CLASS", IDLE_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "NORMAL_PRIORITY_CLASS", NORMAL_PRIORITY_CLASS); + PyModule_AddIntConstant( + module, "REALTIME_PRIORITY_CLASS", REALTIME_PRIORITY_CLASS); + // connection status constants + // http://msdn.microsoft.com/en-us/library/cc669305.aspx + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_CLOSED", MIB_TCP_STATE_CLOSED); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_CLOSING", MIB_TCP_STATE_CLOSING); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_CLOSE_WAIT", MIB_TCP_STATE_CLOSE_WAIT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_LISTEN", MIB_TCP_STATE_LISTEN); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_ESTAB", MIB_TCP_STATE_ESTAB); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_SYN_SENT", MIB_TCP_STATE_SYN_SENT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_SYN_RCVD", MIB_TCP_STATE_SYN_RCVD); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_FIN_WAIT1", MIB_TCP_STATE_FIN_WAIT1); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_FIN_WAIT2", MIB_TCP_STATE_FIN_WAIT2); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_LAST_ACK", MIB_TCP_STATE_LAST_ACK); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_TIME_WAIT", MIB_TCP_STATE_TIME_WAIT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_TIME_WAIT", MIB_TCP_STATE_TIME_WAIT); + PyModule_AddIntConstant( + module, "MIB_TCP_STATE_DELETE_TCB", MIB_TCP_STATE_DELETE_TCB); + PyModule_AddIntConstant( + module, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE); + // ...for internal use in _psutil_windows.py + PyModule_AddIntConstant( + module, "INFINITE", INFINITE); + PyModule_AddIntConstant( + module, "ERROR_ACCESS_DENIED", ERROR_ACCESS_DENIED); + + // set SeDebug for the current process + psutil_set_se_debug(); + +#if PY_MAJOR_VERSION >= 3 + return module; +#endif +} diff --git a/python/psutil/psutil/_psutil_windows.h b/python/psutil/psutil/_psutil_windows.h new file mode 100644 index 0000000000..c77f64e9c3 --- /dev/null +++ b/python/psutil/psutil/_psutil_windows.h @@ -0,0 +1,68 @@ +/* + * 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> + +// --- per-process functions + +static PyObject* psutil_proc_cmdline(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_proc_create_time(PyObject* self, PyObject* args); +static PyObject* psutil_proc_cwd(PyObject* self, PyObject* args); +static PyObject* psutil_proc_exe(PyObject* self, PyObject* args); +static PyObject* psutil_proc_info(PyObject* self, PyObject* args); +static PyObject* psutil_proc_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_proc_is_suspended(PyObject* self, PyObject* args); +static PyObject* psutil_proc_kill(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_info(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_info_2(PyObject* self, PyObject* args); +static PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args); +static PyObject* psutil_proc_name(PyObject* self, PyObject* args); +static PyObject* psutil_proc_num_handles(PyObject* self, PyObject* args); +static PyObject* psutil_proc_open_files(PyObject* self, PyObject* args); +static PyObject* psutil_proc_priority_get(PyObject* self, PyObject* args); +static PyObject* psutil_proc_priority_set(PyObject* self, PyObject* args); +static PyObject* psutil_proc_resume(PyObject* self, PyObject* args); +static PyObject* psutil_proc_suspend(PyObject* self, PyObject* args); +static PyObject* psutil_proc_threads(PyObject* self, PyObject* args); +static PyObject* psutil_proc_username(PyObject* self, PyObject* args); +static PyObject* psutil_proc_wait(PyObject* self, PyObject* args); + +#if (PSUTIL_WINVER >= 0x0600) // Windows Vista +static PyObject* psutil_proc_io_priority_get(PyObject* self, PyObject* args); +static PyObject* psutil_proc_io_priority_set(PyObject* self, PyObject* args); +#endif + +// --- system-related functions + +static PyObject* psutil_boot_time(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_logical(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args); +static PyObject* psutil_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_disk_partitions(PyObject* self, PyObject* args); +static PyObject* psutil_disk_usage(PyObject* self, PyObject* args); +static PyObject* psutil_net_connections(PyObject* self, PyObject* args); +static PyObject* psutil_net_io_counters(PyObject* self, PyObject* args); +static PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args); +static PyObject* psutil_pid_exists(PyObject* self, PyObject* args); +static PyObject* psutil_pids(PyObject* self, PyObject* args); +static PyObject* psutil_ppid_map(PyObject* self, PyObject* args); +static PyObject* psutil_users(PyObject* self, PyObject* args); +static PyObject* psutil_virtual_mem(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_addrs(PyObject* self, PyObject* args); +static PyObject* psutil_net_if_stats(PyObject* self, PyObject* args); + +// --- windows API bindings + +static PyObject* psutil_win32_QueryDosDevice(PyObject* self, PyObject* args); + +// --- internal + +int psutil_proc_suspend_or_resume(DWORD pid, int suspend); diff --git a/python/psutil/psutil/_pswindows.py b/python/psutil/psutil/_pswindows.py new file mode 100644 index 0000000000..2d8babb19c --- /dev/null +++ b/python/psutil/psutil/_pswindows.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python + +# 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. + +"""Windows platform implementation.""" + +import errno +import functools +import os +import sys +from collections import namedtuple + +from . import _common +from . import _psutil_windows as cext +from ._common import conn_tmap, usage_percent, isfile_strict +from ._common import sockfam_to_enum, socktype_to_enum +from ._compat import PY3, xrange, lru_cache, long +from ._psutil_windows import (ABOVE_NORMAL_PRIORITY_CLASS, + BELOW_NORMAL_PRIORITY_CLASS, + HIGH_PRIORITY_CLASS, + IDLE_PRIORITY_CLASS, + NORMAL_PRIORITY_CLASS, + REALTIME_PRIORITY_CLASS) + +if sys.version_info >= (3, 4): + import enum +else: + enum = None + +# process priority constants, import from __init__.py: +# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx +__extra__all__ = ["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", + ] + +# --- module level constants (gets pushed up to psutil module) + +CONN_DELETE_TCB = "DELETE_TCB" +WAIT_TIMEOUT = 0x00000102 # 258 in decimal +ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES, + cext.ERROR_ACCESS_DENIED]) +if enum is None: + AF_LINK = -1 +else: + AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1}) + AF_LINK = AddressFamily.AF_LINK + +TCP_STATUSES = { + cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED, + cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT, + cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV, + cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1, + cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2, + cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT, + cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE, + cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT, + cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK, + cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN, + cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING, + cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB, + cext.PSUTIL_CONN_NONE: _common.CONN_NONE, +} + +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 + HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS + IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS + NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS + REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS + + globals().update(Priority.__members__) + +scputimes = namedtuple('scputimes', ['user', 'system', 'idle']) +svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) +pextmem = namedtuple( + 'pextmem', ['num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool', + 'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool', + 'pagefile', 'peak_pagefile', 'private']) +pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss']) +pmmap_ext = namedtuple( + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) +ntpinfo = namedtuple( + 'ntpinfo', ['num_handles', 'ctx_switches', 'user_time', 'kernel_time', + 'create_time', 'num_threads', 'io_rcount', 'io_wcount', + 'io_rbytes', 'io_wbytes']) + +# set later from __init__.py +NoSuchProcess = None +AccessDenied = None +TimeoutExpired = None + + +@lru_cache(maxsize=512) +def _win32_QueryDosDevice(s): + return cext.win32_QueryDosDevice(s) + + +def _convert_raw_path(s): + # convert paths using native DOS format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # into: "C:\Windows\systemew\file.txt" + if PY3 and not isinstance(s, str): + s = s.decode('utf8') + rawdrive = '\\'.join(s.split('\\')[:3]) + driveletter = _win32_QueryDosDevice(rawdrive) + return os.path.join(driveletter, s[len(rawdrive):]) + + +# --- public functions + + +def virtual_memory(): + """System virtual memory as a namedtuple.""" + mem = cext.virtual_mem() + totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem + # + total = totphys + avail = availphys + free = availphys + used = total - avail + percent = usage_percent((total - avail), total, _round=1) + return svmem(total, avail, percent, used, free) + + +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) + return _common.sswap(total, used, free, percent, 0, 0) + + +def disk_usage(path): + """Return disk usage associated with path.""" + try: + total, free = cext.disk_usage(path) + except WindowsError: + if not os.path.exists(path): + msg = "No such file or directory: '%s'" % path + raise OSError(errno.ENOENT, msg) + raise + used = total - free + percent = usage_percent(used, total, _round=1) + return _common.sdiskusage(total, used, free, percent) + + +def disk_partitions(all): + """Return disk partitions.""" + rawlist = cext.disk_partitions(all) + return [_common.sdiskpart(*x) for x in rawlist] + + +def cpu_times(): + """Return system CPU times as a named tuple.""" + user, system, idle = cext.cpu_times() + return scputimes(user, system, idle) + + +def per_cpu_times(): + """Return system per-CPU times as a list of named tuples.""" + ret = [] + for cpu_t in cext.per_cpu_times(): + user, system, idle = cpu_t + item = scputimes(user, system, idle) + ret.append(item) + return ret + + +def cpu_count_logical(): + """Return the number of logical CPUs in the system.""" + return cext.cpu_count_logical() + + +def cpu_count_physical(): + """Return the number of physical CPUs in the system.""" + return cext.cpu_count_phys() + + +def boot_time(): + """The system boot time expressed in seconds since the epoch.""" + return cext.boot_time() + + +def net_connections(kind, _pid=-1): + """Return socket connections. If pid == -1 return system-wide + 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]))) + 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 + status = TCP_STATUSES[status] + fam = sockfam_to_enum(fam) + type = socktype_to_enum(type) + if _pid == -1: + nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid) + else: + nt = _common.pconn(fd, fam, type, laddr, raddr, status) + ret.add(nt) + return list(ret) + + +def net_if_stats(): + ret = cext.net_if_stats() + for name, items in ret.items(): + isup, duplex, speed, mtu = items + if hasattr(_common, 'NicDuplex'): + duplex = _common.NicDuplex(duplex) + ret[name] = _common.snicstats(isup, duplex, speed, mtu) + return ret + + +def users(): + """Return currently connected users as a list of namedtuples.""" + retlist = [] + rawlist = cext.users() + for item in rawlist: + user, hostname, tstamp = item + nt = _common.suser(user, None, hostname, tstamp) + retlist.append(nt) + return retlist + + +pids = cext.pids +pid_exists = cext.pid_exists +net_io_counters = cext.net_io_counters +disk_io_counters = cext.disk_io_counters +ppid_map = cext.ppid_map # not meant to be public +net_if_addrs = cext.net_if_addrs + + +def wrap_exceptions(fun): + """Decorator which translates bare OSError and WindowsError + exceptions into NoSuchProcess and AccessDenied. + """ + @functools.wraps(fun) + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + # support for private module import + if NoSuchProcess is None or AccessDenied is None: + raise + if err.errno in ACCESS_DENIED_SET: + raise AccessDenied(self.pid, self._name) + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + raise + return wrapper + + +class Process(object): + """Wrapper class around underlying C implementation.""" + + __slots__ = ["pid", "_name", "_ppid"] + + def __init__(self, pid): + self.pid = pid + self._name = None + self._ppid = None + + @wrap_exceptions + def name(self): + """Return process name, which on Windows is always the final + part of the executable. + """ + # This is how PIDs 0 and 4 are always represented in taskmgr + # and process-hacker. + if self.pid == 0: + return "System Idle Process" + elif self.pid == 4: + return "System" + else: + try: + # Note: this will fail with AD for most PIDs owned + # by another user but it's faster. + return os.path.basename(self.exe()) + except AccessDenied: + return cext.proc_name(self.pid) + + @wrap_exceptions + def exe(self): + # Note: os.path.exists(path) may return False even if the file + # is there, see: + # http://stackoverflow.com/questions/3112546/os-path-exists-lies + + # see https://github.com/giampaolo/psutil/issues/414 + # see https://github.com/giampaolo/psutil/issues/528 + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + return _convert_raw_path(cext.proc_exe(self.pid)) + + @wrap_exceptions + def cmdline(self): + return cext.proc_cmdline(self.pid) + + def ppid(self): + try: + return ppid_map()[self.pid] + except KeyError: + raise NoSuchProcess(self.pid, self._name) + + def _get_raw_meminfo(self): + try: + return cext.proc_memory_info(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + # TODO: the C ext can probably be refactored in order + # to get this from cext.proc_info() + return cext.proc_memory_info_2(self.pid) + raise + + @wrap_exceptions + def memory_info(self): + # on Windows RSS == WorkingSetSize and VSM == PagefileUsage + # fields of PROCESS_MEMORY_COUNTERS struct: + # http://msdn.microsoft.com/en-us/library/windows/desktop/ + # ms684877(v=vs.85).aspx + t = self._get_raw_meminfo() + return _common.pmem(t[2], t[7]) + + @wrap_exceptions + def memory_info_ex(self): + return pextmem(*self._get_raw_meminfo()) + + def memory_maps(self): + try: + raw = cext.proc_memory_maps(self.pid) + except OSError as err: + # XXX - can't use wrap_exceptions decorator as we're + # returning a generator; probably needs refactoring. + if err.errno in ACCESS_DENIED_SET: + raise AccessDenied(self.pid, self._name) + if err.errno == errno.ESRCH: + raise NoSuchProcess(self.pid, self._name) + raise + else: + for addr, perm, path, rss in raw: + path = _convert_raw_path(path) + addr = hex(addr) + yield (addr, perm, path, rss) + + @wrap_exceptions + def kill(self): + return cext.proc_kill(self.pid) + + @wrap_exceptions + def wait(self, timeout=None): + if timeout is None: + timeout = cext.INFINITE + else: + # WaitForSingleObject() expects time in milliseconds + timeout = int(timeout * 1000) + ret = cext.proc_wait(self.pid, timeout) + if ret == WAIT_TIMEOUT: + # support for private module import + if TimeoutExpired is None: + raise RuntimeError("timeout expired") + raise TimeoutExpired(timeout, self.pid, self._name) + return ret + + @wrap_exceptions + def username(self): + if self.pid in (0, 4): + return 'NT AUTHORITY\\SYSTEM' + return cext.proc_username(self.pid) + + @wrap_exceptions + def create_time(self): + # special case for kernel process PIDs; return system boot time + if self.pid in (0, 4): + return boot_time() + try: + return cext.proc_create_time(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + return ntpinfo(*cext.proc_info(self.pid)).create_time + raise + + @wrap_exceptions + def num_threads(self): + return ntpinfo(*cext.proc_info(self.pid)).num_threads + + @wrap_exceptions + def threads(self): + rawlist = cext.proc_threads(self.pid) + retlist = [] + for thread_id, utime, stime in rawlist: + ntuple = _common.pthread(thread_id, utime, stime) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def cpu_times(self): + try: + ret = cext.proc_cpu_times(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + nt = ntpinfo(*cext.proc_info(self.pid)) + ret = (nt.user_time, nt.kernel_time) + else: + raise + return _common.pcputimes(*ret) + + @wrap_exceptions + def suspend(self): + return cext.proc_suspend(self.pid) + + @wrap_exceptions + def resume(self): + return cext.proc_resume(self.pid) + + @wrap_exceptions + def cwd(self): + if self.pid in (0, 4): + raise AccessDenied(self.pid, self._name) + # return a normalized pathname since the native C function appends + # "\\" at the and of the path + path = cext.proc_cwd(self.pid) + return os.path.normpath(path) + + @wrap_exceptions + def open_files(self): + if self.pid in (0, 4): + return [] + retlist = [] + # Filenames come in in native format like: + # "\Device\HarddiskVolume1\Windows\systemew\file.txt" + # Convert the first part in the corresponding drive letter + # (e.g. "C:\") by using Windows's QueryDosDevice() + raw_file_names = cext.proc_open_files(self.pid) + for _file in raw_file_names: + _file = _convert_raw_path(_file) + if isfile_strict(_file) and _file not in retlist: + ntuple = _common.popenfile(_file, -1) + retlist.append(ntuple) + return retlist + + @wrap_exceptions + def connections(self, kind='inet'): + return net_connections(kind, _pid=self.pid) + + @wrap_exceptions + def nice_get(self): + value = cext.proc_priority_get(self.pid) + if enum is not None: + value = Priority(value) + return value + + @wrap_exceptions + def nice_set(self, value): + return cext.proc_priority_set(self.pid, value) + + # available on Windows >= Vista + if hasattr(cext, "proc_io_priority_get"): + @wrap_exceptions + def ionice_get(self): + return cext.proc_io_priority_get(self.pid) + + @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) + + @wrap_exceptions + def io_counters(self): + try: + ret = cext.proc_io_counters(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + nt = ntpinfo(*cext.proc_info(self.pid)) + ret = (nt.io_rcount, nt.io_wcount, nt.io_rbytes, nt.io_wbytes) + else: + raise + return _common.pio(*ret) + + @wrap_exceptions + def status(self): + suspended = cext.proc_is_suspended(self.pid) + if suspended: + return _common.STATUS_STOPPED + else: + return _common.STATUS_RUNNING + + @wrap_exceptions + def cpu_affinity_get(self): + def from_bitmask(x): + return [i for i in xrange(64) if (1 << i) & x] + bitmask = cext.proc_cpu_affinity_get(self.pid) + return from_bitmask(bitmask) + + @wrap_exceptions + def cpu_affinity_set(self, value): + def to_bitmask(l): + if not l: + raise ValueError("invalid argument %r" % l) + out = 0 + for b in l: + out |= 2 ** b + return out + + # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER + # is returned for an invalid CPU but this seems not to be true, + # therefore we check CPUs validy beforehand. + allcpus = list(range(len(per_cpu_times()))) + for cpu in value: + if cpu not in allcpus: + if not isinstance(cpu, (int, long)): + raise TypeError( + "invalid CPU %r; an integer is required" % cpu) + else: + raise ValueError("invalid CPU %r" % cpu) + + bitmask = to_bitmask(value) + cext.proc_cpu_affinity_set(self.pid, bitmask) + + @wrap_exceptions + def num_handles(self): + try: + return cext.proc_num_handles(self.pid) + except OSError as err: + if err.errno in ACCESS_DENIED_SET: + return ntpinfo(*cext.proc_info(self.pid)).num_handles + raise + + @wrap_exceptions + def num_ctx_switches(self): + ctx_switches = ntpinfo(*cext.proc_info(self.pid)).ctx_switches + # only voluntary ctx switches are supported + return _common.pctxsw(ctx_switches, 0) diff --git a/python/psutil/psutil/arch/bsd/process_info.c b/python/psutil/psutil/arch/bsd/process_info.c new file mode 100644 index 0000000000..4d73924061 --- /dev/null +++ b/python/psutil/psutil/arch/bsd/process_info.c @@ -0,0 +1,265 @@ +/* + * 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_bsd module methods. + */ + + +#include <Python.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/sysctl.h> +#include <sys/param.h> +#include <sys/user.h> +#include <sys/proc.h> +#include <signal.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(struct kinfo_proc **procList, size_t *procCount) +{ + int err; + struct kinfo_proc *result; + int done; + static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0 }; + // Declaring name as const requires us to cast it when passing it to + // sysctl because the prototype doesn't include the const modifier. + size_t length; + + assert( procList != NULL); + assert(*procList == NULL); + assert(procCount != NULL); + + *procCount = 0; + + /* + * We start by calling sysctl with result == NULL and length == 0. + * That will succeed, and set length to the appropriate length. + * We then allocate a buffer of that size and call sysctl again + * with that buffer. If that succeeds, we're done. If that fails + * with ENOMEM, we have to throw away our buffer and loop. Note + * that the loop causes use to call sysctl with NULL again; this + * is necessary because the ENOMEM failure case sets length to + * the amount of data returned, not the amount of data that + * could have been returned. + */ + result = NULL; + done = 0; + do { + assert(result == NULL); + // Call sysctl with a NULL buffer. + length = 0; + err = sysctl((int *)name, (sizeof(name) / sizeof(*name)) - 1, + NULL, &length, NULL, 0); + if (err == -1) + err = errno; + + // Allocate an appropriately sized buffer based on the results + // from the previous call. + if (err == 0) { + result = malloc(length); + if (result == NULL) + err = ENOMEM; + } + + // Call sysctl again with the new buffer. If we get an ENOMEM + // error, toss away our buffer and start again. + if (err == 0) { + err = sysctl((int *) name, (sizeof(name) / sizeof(*name)) - 1, + result, &length, NULL, 0); + if (err == -1) + err = errno; + if (err == 0) { + done = 1; + } + else if (err == ENOMEM) { + assert(result != NULL); + free(result); + result = NULL; + err = 0; + } + } + } while (err == 0 && ! done); + + // Clean up and establish post conditions. + if (err != 0 && result != NULL) { + free(result); + result = NULL; + } + + *procList = result; + *procCount = length / sizeof(struct kinfo_proc); + + assert((err == 0) == (*procList != NULL)); + return err; +} + + +char +*psutil_get_cmd_path(long pid, size_t *pathsize) +{ + int mib[4]; + char *path; + size_t size = 0; + + /* + * Make a sysctl() call to get the raw argument space of the process. + */ + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = pid; + + // call with a null buffer first to determine if we need a buffer + if (sysctl(mib, 4, NULL, &size, NULL, 0) == -1) + return NULL; + + path = malloc(size); + if (path == NULL) { + PyErr_NoMemory(); + return NULL; + } + + *pathsize = size; + if (sysctl(mib, 4, path, &size, NULL, 0) == -1) { + free(path); + return NULL; // Insufficient privileges + } + + return path; +} + + +/* + * XXX no longer used; it probably makese sense to remove it. + * Borrowed from psi Python System Information project + * + * Get command arguments and environment variables. + * + * Based on code from ps. + * + * Returns: + * 0 for success; + * -1 for failure (Exception raised); + * 1 for insufficient privileges. + */ +char +*psutil_get_cmd_args(long pid, size_t *argsize) +{ + int mib[4], argmax; + size_t size = sizeof(argmax); + char *procargs = NULL; + + // Get the maximum process arguments size. + mib[0] = CTL_KERN; + mib[1] = KERN_ARGMAX; + + size = sizeof(argmax); + if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) + return NULL; + + // Allocate space for the arguments. + procargs = (char *)malloc(argmax); + if (procargs == NULL) { + PyErr_NoMemory(); + return NULL; + } + + /* + * Make a sysctl() call to get the raw argument space of the process. + */ + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ARGS; + mib[3] = pid; + + size = argmax; + if (sysctl(mib, 4, procargs, &size, NULL, 0) == -1) { + free(procargs); + return NULL; // Insufficient privileges + } + + // return string and set the length of arguments + *argsize = size; + return procargs; +} + + +// returns the command line as a python list object +PyObject * +psutil_get_arg_list(long pid) +{ + char *argstr = NULL; + int pos = 0; + size_t argsize = 0; + PyObject *retlist = Py_BuildValue("[]"); + PyObject *item = NULL; + + if (pid < 0) + return retlist; + argstr = psutil_get_cmd_args(pid, &argsize); + if (argstr == NULL) + goto error; + + // args are returned as a flattened string with \0 separators between + // arguments add each string to the list then step forward to the next + // separator + if (argsize > 0) { + while (pos < argsize) { + item = Py_BuildValue("s", &argstr[pos]); + if (!item) + goto error; + if (PyList_Append(retlist, item)) + goto error; + Py_DECREF(item); + pos = pos + strlen(&argstr[pos]) + 1; + } + } + + free(argstr); + return retlist; + +error: + Py_XDECREF(item); + Py_DECREF(retlist); + if (argstr != NULL) + free(argstr); + return NULL; +} + + +/* + * Return 1 if PID exists in the current process list, else 0. + */ +int +psutil_pid_exists(long pid) +{ + int kill_ret; + + if (pid < 0) + return 0; + // if kill returns success of permission denied we know it's a valid PID + kill_ret = kill(pid , 0); + if ((0 == kill_ret) || (EPERM == errno)) + return 1; + // otherwise return 0 for PID not found + return 0; +} + diff --git a/python/psutil/psutil/arch/bsd/process_info.h b/python/psutil/psutil/arch/bsd/process_info.h new file mode 100644 index 0000000000..858bd88a57 --- /dev/null +++ b/python/psutil/psutil/arch/bsd/process_info.h @@ -0,0 +1,15 @@ +/* + * 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; + +char *psutil_get_cmd_args(long pid, size_t *argsize); +char *psutil_get_cmd_path(long pid, size_t *pathsize); +int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount); +int psutil_pid_exists(long pid); +PyObject* psutil_get_arg_list(long pid); diff --git a/python/psutil/psutil/arch/osx/process_info.c b/python/psutil/psutil/arch/osx/process_info.c new file mode 100644 index 0000000000..b6dd5bb938 --- /dev/null +++ b/python/psutil/psutil/arch/osx/process_info.c @@ -0,0 +1,281 @@ +/* + * 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 <assert.h> +#include <errno.h> +#include <limits.h> // for INT_MAX +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <sys/sysctl.h> +#include <libproc.h> + +#include "process_info.h" +#include "../../_psutil_common.h" + + +/* + * Return 1 if PID exists in the current process list, else 0. + */ +int +psutil_pid_exists(long pid) +{ + int kill_ret; + + // save some time if it's an invalid PID + if (pid < 0) + return 0; + // if kill returns success of permission denied we know it's a valid PID + kill_ret = kill(pid , 0); + if ( (0 == kill_ret) || (EPERM == errno)) + return 1; + + // otherwise return 0 for PID not found + return 0; +} + + +/* + * 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) +{ + // Declaring mib as const requires use of a cast since the + // sysctl prototype doesn't include the const modifier. + static const int mib3[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL }; + size_t size, size2; + void *ptr; + int err, lim = 8; // some limit + + assert( procList != NULL); + assert(*procList == NULL); + assert(procCount != NULL); + + *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 *)mib3, 3, NULL, &size, NULL, 0) == -1) + return errno; + 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) + return ENOMEM; + + if (sysctl((int *)mib3, 3, ptr, &size, NULL, 0) == -1) { + err = errno; + free(ptr); + if (err != ENOMEM) + return err; + } + else { + *procList = (kinfo_proc *)ptr; + *procCount = size / sizeof(kinfo_proc); + return 0; + } + } + return ENOMEM; +} + + +// Read the maximum argument size for processes +int +psutil_get_argmax() +{ + int argmax; + int mib[] = { CTL_KERN, KERN_ARGMAX }; + size_t size = sizeof(argmax); + + if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) + return argmax; + return 0; +} + + +// return process args as a python list +PyObject * +psutil_get_arg_list(long pid) +{ + int mib[3]; + int nargs; + int len; + char *procargs = NULL; + char *arg_ptr; + char *arg_end; + char *curr_arg; + size_t argmax; + PyObject *arg = NULL; + PyObject *arglist = 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_get_argmax(); + if (! argmax) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + procargs = (char *)malloc(argmax); + if (NULL == procargs) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + // read argument space + mib[0] = CTL_KERN; + mib[1] = KERN_PROCARGS2; + mib[2] = pid; + if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { + if (EINVAL == errno) { + // EINVAL == access denied OR nonexistent PID + if (psutil_pid_exists(pid)) + AccessDenied(); + else + NoSuchProcess(); + } + 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; + arglist = Py_BuildValue("[]"); + if (!arglist) + goto error; + while (arg_ptr < arg_end && nargs > 0) { + if (*arg_ptr++ == '\0') { + arg = Py_BuildValue("s", curr_arg); + if (!arg) + goto error; + if (PyList_Append(arglist, arg)) + goto error; + Py_DECREF(arg); + // iterate to next arg and decrement # of args + curr_arg = arg_ptr; + nargs--; + } + } + + free(procargs); + return arglist; + +error: + Py_XDECREF(arg); + Py_XDECREF(arglist); + if (procargs != 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_SetFromErrno(PyExc_OSError); + return -1; + } + + // sysctl succeeds but len is zero, happens when process has gone away + if (len == 0) { + NoSuchProcess(); + return -1; + } + return 0; +} + + +/* + * A thin wrapper around proc_pidinfo() + */ +int +psutil_proc_pidinfo(long pid, int flavor, void *pti, int size) +{ + int ret = proc_pidinfo((int)pid, flavor, 0, pti, size); + if (ret == 0) { + if (! psutil_pid_exists(pid)) { + NoSuchProcess(); + return 0; + } + else { + AccessDenied(); + return 0; + } + } + else if (ret != size) { + AccessDenied(); + return 0; + } + else { + return 1; + } +} diff --git a/python/psutil/psutil/arch/osx/process_info.h b/python/psutil/psutil/arch/osx/process_info.h new file mode 100644 index 0000000000..c89c8570e7 --- /dev/null +++ b/python/psutil/psutil/arch/osx/process_info.h @@ -0,0 +1,16 @@ +/* + * 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_get_argmax(void); +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_pid_exists(long pid); +int psutil_proc_pidinfo(long pid, int flavor, void *pti, int size); +PyObject* psutil_get_arg_list(long pid); diff --git a/python/psutil/psutil/arch/windows/glpi.h b/python/psutil/psutil/arch/windows/glpi.h new file mode 100644 index 0000000000..6f98483733 --- /dev/null +++ b/python/psutil/psutil/arch/windows/glpi.h @@ -0,0 +1,41 @@ +// mingw headers are missing this + +typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP { + RelationProcessorCore, + RelationNumaNode, + RelationCache, + RelationProcessorPackage, + RelationGroup, + RelationAll=0xffff +} LOGICAL_PROCESSOR_RELATIONSHIP; + +typedef enum _PROCESSOR_CACHE_TYPE { + CacheUnified,CacheInstruction,CacheData,CacheTrace +} PROCESSOR_CACHE_TYPE; + +typedef struct _CACHE_DESCRIPTOR { + BYTE Level; + BYTE Associativity; + WORD LineSize; + DWORD Size; + PROCESSOR_CACHE_TYPE Type; +} CACHE_DESCRIPTOR,*PCACHE_DESCRIPTOR; + +typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION { + ULONG_PTR ProcessorMask; + LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + union { + struct { + BYTE Flags; + } ProcessorCore; + struct { + DWORD NodeNumber; + } NumaNode; + CACHE_DESCRIPTOR Cache; + ULONGLONG Reserved[2]; + }; +} SYSTEM_LOGICAL_PROCESSOR_INFORMATION,*PSYSTEM_LOGICAL_PROCESSOR_INFORMATION; + +WINBASEAPI WINBOOL WINAPI +GetLogicalProcessorInformation(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer, + PDWORD ReturnedLength);
\ No newline at end of file diff --git a/python/psutil/psutil/arch/windows/inet_ntop.c b/python/psutil/psutil/arch/windows/inet_ntop.c new file mode 100644 index 0000000000..b9fffd1c16 --- /dev/null +++ b/python/psutil/psutil/arch/windows/inet_ntop.c @@ -0,0 +1,41 @@ +#include "inet_ntop.h" + +// From: https://memset.wordpress.com/2010/10/09/inet_ntop-for-win32/ +PCSTR +WSAAPI +inet_ntop( + __in INT Family, + __in PVOID pAddr, + __out_ecount(StringBufSize) PSTR pStringBuf, + __in size_t StringBufSize + ) +{ + DWORD dwAddressLength = 0; + struct sockaddr_storage srcaddr; + struct sockaddr_in *srcaddr4 = (struct sockaddr_in*) &srcaddr; + struct sockaddr_in6 *srcaddr6 = (struct sockaddr_in6*) &srcaddr; + + memset(&srcaddr, 0, sizeof(struct sockaddr_storage)); + srcaddr.ss_family = Family; + + if (Family == AF_INET) + { + dwAddressLength = sizeof(struct sockaddr_in); + memcpy(&(srcaddr4->sin_addr), pAddr, sizeof(struct in_addr)); + } else if (Family == AF_INET6) + { + dwAddressLength = sizeof(struct sockaddr_in6); + memcpy(&(srcaddr6->sin6_addr), pAddr, sizeof(struct in6_addr)); + } else { + return NULL; + } + + if (WSAAddressToString((LPSOCKADDR) &srcaddr, + dwAddressLength, + 0, + pStringBuf, + (LPDWORD) &StringBufSize) != 0) { + return NULL; + } + return pStringBuf; +}
\ No newline at end of file diff --git a/python/psutil/psutil/arch/windows/inet_ntop.h b/python/psutil/psutil/arch/windows/inet_ntop.h new file mode 100644 index 0000000000..0d97e28c88 --- /dev/null +++ b/python/psutil/psutil/arch/windows/inet_ntop.h @@ -0,0 +1,10 @@ +#include <ws2tcpip.h> + +PCSTR +WSAAPI +inet_ntop( + __in INT Family, + __in PVOID pAddr, + __out_ecount(StringBufSize) PSTR pStringBuf, + __in size_t StringBufSize +);
\ No newline at end of file diff --git a/python/psutil/psutil/arch/windows/ntextapi.h b/python/psutil/psutil/arch/windows/ntextapi.h new file mode 100644 index 0000000000..d10432a3e3 --- /dev/null +++ b/python/psutil/psutil/arch/windows/ntextapi.h @@ -0,0 +1,228 @@ +/* + * 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. + */ +#if !defined(__NTEXTAPI_H__) +#define __NTEXTAPI_H__ +#include <winternl.h> + +typedef enum _KTHREAD_STATE { + Initialized, + Ready, + Running, + Standby, + Terminated, + Waiting, + Transition, + DeferredReady, + GateWait, + MaximumThreadState +} KTHREAD_STATE, *PKTHREAD_STATE; + +typedef enum _KWAIT_REASON { + Executive = 0, + FreePage = 1, + PageIn = 2, + PoolAllocation = 3, + DelayExecution = 4, + Suspended = 5, + UserRequest = 6, + WrExecutive = 7, + WrFreePage = 8, + WrPageIn = 9, + WrPoolAllocation = 10, + WrDelayExecution = 11, + WrSuspended = 12, + WrUserRequest = 13, + WrEventPair = 14, + WrQueue = 15, + WrLpcReceive = 16, + WrLpcReply = 17, + WrVirtualMemory = 18, + WrPageOut = 19, + WrRendezvous = 20, + Spare2 = 21, + Spare3 = 22, + Spare4 = 23, + Spare5 = 24, + WrCalloutStack = 25, + WrKernel = 26, + WrResource = 27, + WrPushLock = 28, + WrMutex = 29, + WrQuantumEnd = 30, + WrDispatchInt = 31, + WrPreempted = 32, + WrYieldExecution = 33, + WrFastMutex = 34, + WrGuardedMutex = 35, + WrRundown = 36, + MaximumWaitReason = 37 +} KWAIT_REASON, *PKWAIT_REASON; + +typedef struct _CLIENT_ID { + HANDLE UniqueProcess; + HANDLE UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef struct _SYSTEM_THREAD_INFORMATION { + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + ULONG WaitTime; + PVOID StartAddress; + CLIENT_ID ClientId; + LONG Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + KWAIT_REASON WaitReason; +} SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION; + +typedef struct _TEB *PTEB; + +// private +typedef struct _SYSTEM_EXTENDED_THREAD_INFORMATION { + SYSTEM_THREAD_INFORMATION ThreadInfo; + PVOID StackBase; + PVOID StackLimit; + PVOID Win32StartAddress; + PTEB TebBase; + ULONG_PTR Reserved2; + ULONG_PTR Reserved3; + ULONG_PTR Reserved4; +} SYSTEM_EXTENDED_THREAD_INFORMATION, *PSYSTEM_EXTENDED_THREAD_INFORMATION; + +typedef struct _SYSTEM_PROCESS_INFORMATION2 { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER SpareLi1; + LARGE_INTEGER SpareLi2; + LARGE_INTEGER SpareLi3; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING ImageName; + LONG BasePriority; + HANDLE UniqueProcessId; + HANDLE InheritedFromUniqueProcessId; + ULONG HandleCount; + ULONG SessionId; + ULONG_PTR PageDirectoryBase; + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER ReadOperationCount; + LARGE_INTEGER WriteOperationCount; + LARGE_INTEGER OtherOperationCount; + LARGE_INTEGER ReadTransferCount; + LARGE_INTEGER WriteTransferCount; + LARGE_INTEGER OtherTransferCount; + SYSTEM_THREAD_INFORMATION Threads[1]; +} SYSTEM_PROCESS_INFORMATION2, *PSYSTEM_PROCESS_INFORMATION2; + +#define SYSTEM_PROCESS_INFORMATION SYSTEM_PROCESS_INFORMATION2 +#define PSYSTEM_PROCESS_INFORMATION PSYSTEM_PROCESS_INFORMATION2 + + +// ================================================ +// psutil.users() support +// ================================================ + +typedef struct _WINSTATION_INFO { + BYTE Reserved1[72]; + ULONG SessionId; + BYTE Reserved2[4]; + FILETIME ConnectTime; + FILETIME DisconnectTime; + FILETIME LastInputTime; + FILETIME LoginTime; + BYTE Reserved3[1096]; + FILETIME CurrentTime; +} WINSTATION_INFO, *PWINSTATION_INFO; + +typedef BOOLEAN (WINAPI * PWINSTATIONQUERYINFORMATIONW) + (HANDLE,ULONG,WINSTATIONINFOCLASS,PVOID,ULONG,PULONG); + + +/* + * NtQueryInformationProcess code taken from + * http://wj32.wordpress.com/2009/01/24/howto-get-the-command-line-of-processes/ + * typedefs needed to compile against ntdll functions not exposted in the API + */ +typedef LONG NTSTATUS; + +typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)( + HANDLE ProcessHandle, + DWORD ProcessInformationClass, + PVOID ProcessInformation, + DWORD ProcessInformationLength, + PDWORD ReturnLength +); + +typedef NTSTATUS (NTAPI *_NtSetInformationProcess)( + HANDLE ProcessHandle, + DWORD ProcessInformationClass, + PVOID ProcessInformation, + DWORD ProcessInformationLength +); + + +typedef enum _PROCESSINFOCLASS2 { + _ProcessBasicInformation, + ProcessQuotaLimits, + ProcessIoCounters, + ProcessVmCounters, + ProcessTimes, + ProcessBasePriority, + ProcessRaisePriority, + ProcessDebugPort, + ProcessExceptionPort, + ProcessAccessToken, + ProcessLdtInformation, + ProcessLdtSize, + ProcessDefaultHardErrorMode, + ProcessIoPortHandlers, + ProcessPooledUsageAndLimits, + ProcessWorkingSetWatch, + ProcessUserModeIOPL, + ProcessEnableAlignmentFaultFixup, + ProcessPriorityClass, + ProcessWx86Information, + ProcessHandleCount, + ProcessAffinityMask, + ProcessPriorityBoost, + ProcessDeviceMap, + ProcessSessionInformation, + ProcessForegroundInformation, + _ProcessWow64Information, + /* added after XP+ */ + ProcessImageFileName, + ProcessLUIDDeviceMapsEnabled, + ProcessBreakOnTermination, + ProcessDebugObjectHandle, + ProcessDebugFlags, + ProcessHandleTracing, + ProcessIoPriority, + ProcessExecuteFlags, + ProcessResourceManagement, + ProcessCookie, + ProcessImageInformation, + MaxProcessInfoClass +} PROCESSINFOCLASS2; + +#define PROCESSINFOCLASS PROCESSINFOCLASS2 +#define ProcessBasicInformation _ProcessBasicInformation +#define ProcessWow64Information _ProcessWow64Information + +#endif // __NTEXTAPI_H__ diff --git a/python/psutil/psutil/arch/windows/process_handles.c b/python/psutil/psutil/arch/windows/process_handles.c new file mode 100644 index 0000000000..b3f480af54 --- /dev/null +++ b/python/psutil/psutil/arch/windows/process_handles.c @@ -0,0 +1,533 @@ +/* + * 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 "process_handles.h" + +static _NtQuerySystemInformation __NtQuerySystemInformation = NULL; +static _NtQueryObject __NtQueryObject = NULL; + +CRITICAL_SECTION g_cs; +BOOL g_initialized = FALSE; +NTSTATUS g_status; +HANDLE g_hFile = NULL; +HANDLE g_hEvtStart = NULL; +HANDLE g_hEvtFinish = NULL; +HANDLE g_hThread = NULL; +PUNICODE_STRING g_pNameBuffer = NULL; +ULONG g_dwSize = 0; +ULONG g_dwLength = 0; +PVOID g_fiber = NULL; + + +PVOID +GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName) +{ + return GetProcAddress(GetModuleHandleA(LibraryName), ProcName); +} + +PyObject * +psutil_get_open_files(long dwPid, HANDLE hProcess) +{ + OSVERSIONINFO osvi; + + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + + // Threaded version only works for Vista+ + if (osvi.dwMajorVersion >= 6) + return psutil_get_open_files_ntqueryobject(dwPid, hProcess); + else + return psutil_get_open_files_getmappedfilename(dwPid, hProcess); +} + +VOID +psutil_get_open_files_init(BOOL threaded) +{ + if (g_initialized == TRUE) + return; + + // Resolve the Windows API calls + __NtQuerySystemInformation = + GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation"); + __NtQueryObject = GetLibraryProcAddress("ntdll.dll", "NtQueryObject"); + + // Create events for signalling work between threads + if (threaded == TRUE) { + g_hEvtStart = CreateEvent(NULL, FALSE, FALSE, NULL); + g_hEvtFinish = CreateEvent(NULL, FALSE, FALSE, NULL); + InitializeCriticalSection(&g_cs); + } + + g_initialized = TRUE; +} + +PyObject * +psutil_get_open_files_ntqueryobject(long dwPid, HANDLE hProcess) +{ + NTSTATUS status; + PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; + DWORD dwInfoSize = 0x10000; + DWORD dwRet = 0; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL; + DWORD i = 0; + BOOLEAN error = FALSE; + PyObject* pyListFiles = NULL; + PyObject* pyFilePath = NULL; + DWORD dwWait = 0; + + if (g_initialized == FALSE) + psutil_get_open_files_init(TRUE); + + // Due to the use of global variables, ensure only 1 call + // to psutil_get_open_files() is running + EnterCriticalSection(&g_cs); + + if (__NtQuerySystemInformation == NULL || + __NtQueryObject == NULL || + g_hEvtStart == NULL || + g_hEvtFinish == NULL) + + { + PyErr_SetFromWindowsErr(0); + error = TRUE; + goto cleanup; + } + + // Py_BuildValue raises an exception if NULL is returned + pyListFiles = PyList_New(0); + if (pyListFiles == NULL) { + error = TRUE; + goto cleanup; + } + + do { + if (pHandleInfo != NULL) { + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + } + + // NtQuerySystemInformation won't give us the correct buffer size, + // so we guess by doubling the buffer size. + dwInfoSize *= 2; + pHandleInfo = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + dwInfoSize); + + if (pHandleInfo == NULL) { + PyErr_NoMemory(); + error = TRUE; + goto cleanup; + } + } while ((status = __NtQuerySystemInformation( + SystemExtendedHandleInformation, + pHandleInfo, + dwInfoSize, + &dwRet)) == STATUS_INFO_LENGTH_MISMATCH); + + // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH + if (!NT_SUCCESS(status)) { + PyErr_SetFromWindowsErr(HRESULT_FROM_NT(status)); + error = TRUE; + goto cleanup; + } + + for (i = 0; i < pHandleInfo->NumberOfHandles; i++) { + hHandle = &pHandleInfo->Handles[i]; + + // Check if this hHandle belongs to the PID the user specified. + if (hHandle->UniqueProcessId != (HANDLE)dwPid || + hHandle->ObjectTypeIndex != HANDLE_TYPE_FILE) + goto loop_cleanup; + + if (!DuplicateHandle(hProcess, + hHandle->HandleValue, + GetCurrentProcess(), + &g_hFile, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + { + /* + printf("[%d] DuplicateHandle (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + // Guess buffer size is MAX_PATH + 1 + g_dwLength = (MAX_PATH+1) * sizeof(WCHAR); + + do { + // Release any previously allocated buffer + if (g_pNameBuffer != NULL) { + HeapFree(GetProcessHeap(), 0, g_pNameBuffer); + g_pNameBuffer = NULL; + g_dwSize = 0; + } + + // NtQueryObject puts the required buffer size in g_dwLength + // WinXP edge case puts g_dwLength == 0, just skip this handle + if (g_dwLength == 0) + goto loop_cleanup; + + g_dwSize = g_dwLength; + if (g_dwSize > 0) { + g_pNameBuffer = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + g_dwSize); + + if (g_pNameBuffer == NULL) + goto loop_cleanup; + } + + dwWait = psutil_NtQueryObject(); + + // If the call does not return, skip this handle + if (dwWait != WAIT_OBJECT_0) + goto loop_cleanup; + + } while (g_status == STATUS_INFO_LENGTH_MISMATCH); + + // NtQueryObject stopped returning STATUS_INFO_LENGTH_MISMATCH + if (!NT_SUCCESS(g_status)) + goto loop_cleanup; + + // Convert to PyUnicode and append it to the return list + if (g_pNameBuffer->Length > 0) { + /* + printf("[%d] Filename (%#x) %#d bytes: %S\n", + dwPid, + hHandle->HandleValue, + g_pNameBuffer->Length, + g_pNameBuffer->Buffer); + */ + + pyFilePath = PyUnicode_FromWideChar(g_pNameBuffer->Buffer, + g_pNameBuffer->Length/2); + if (pyFilePath == NULL) { + /* + printf("[%d] PyUnicode_FromWideChar (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + + if (PyList_Append(pyListFiles, pyFilePath)) { + /* + printf("[%d] PyList_Append (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + } + +loop_cleanup: + Py_XDECREF(pyFilePath); + pyFilePath = NULL; + + if (g_pNameBuffer != NULL) + HeapFree(GetProcessHeap(), 0, g_pNameBuffer); + g_pNameBuffer = NULL; + g_dwSize = 0; + g_dwLength = 0; + + if (g_hFile != NULL) + CloseHandle(g_hFile); + g_hFile = NULL; + } + +cleanup: + if (g_pNameBuffer != NULL) + HeapFree(GetProcessHeap(), 0, g_pNameBuffer); + g_pNameBuffer = NULL; + g_dwSize = 0; + g_dwLength = 0; + + if (g_hFile != NULL) + CloseHandle(g_hFile); + g_hFile = NULL; + + if (pHandleInfo != NULL) + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + + if (error) { + Py_XDECREF(pyListFiles); + pyListFiles = NULL; + } + + LeaveCriticalSection(&g_cs); + + return pyListFiles; +} + +DWORD +psutil_NtQueryObject() +{ + DWORD dwWait = 0; + + if (g_hThread == NULL) + g_hThread = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE)psutil_NtQueryObjectThread, + NULL, + 0, + NULL); + if (g_hThread == NULL) + return GetLastError(); + + // Signal the worker thread to start + SetEvent(g_hEvtStart); + + // Wait for the worker thread to finish + dwWait = WaitForSingleObject(g_hEvtFinish, NTQO_TIMEOUT); + + // If the thread hangs, kill it and cleanup + if (dwWait == WAIT_TIMEOUT) { + SuspendThread(g_hThread); + TerminateThread(g_hThread, 1); + WaitForSingleObject(g_hThread, INFINITE); + CloseHandle(g_hThread); + + // Cleanup Fiber + if (g_fiber != NULL) + DeleteFiber(g_fiber); + g_fiber = NULL; + + g_hThread = NULL; + } + + return dwWait; +} + +void +psutil_NtQueryObjectThread() +{ + // Prevent the thread stack from leaking when this + // thread gets terminated due to NTQueryObject hanging + g_fiber = ConvertThreadToFiber(NULL); + + // Loop infinitely waiting for work + while (TRUE) { + WaitForSingleObject(g_hEvtStart, INFINITE); + + g_status = __NtQueryObject(g_hFile, + ObjectNameInformation, + g_pNameBuffer, + g_dwSize, + &g_dwLength); + SetEvent(g_hEvtFinish); + } +} + +PyObject * +psutil_get_open_files_getmappedfilename(long dwPid, HANDLE hProcess) +{ + NTSTATUS status; + PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo = NULL; + DWORD dwInfoSize = 0x10000; + DWORD dwRet = 0; + PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL; + HANDLE hFile = NULL; + HANDLE hMap = NULL; + DWORD i = 0; + BOOLEAN error = FALSE; + PyObject* pyListFiles = NULL; + PyObject* pyFilePath = NULL; + ULONG dwSize = 0; + LPVOID pMem = NULL; + TCHAR pszFilename[MAX_PATH+1]; + + if (g_initialized == FALSE) + psutil_get_open_files_init(FALSE); + + if (__NtQuerySystemInformation == NULL || __NtQueryObject == NULL) { + PyErr_SetFromWindowsErr(0); + error = TRUE; + goto cleanup; + } + + // Py_BuildValue raises an exception if NULL is returned + pyListFiles = PyList_New(0); + if (pyListFiles == NULL) { + error = TRUE; + goto cleanup; + } + + do { + if (pHandleInfo != NULL) { + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + } + + // NtQuerySystemInformation won't give us the correct buffer size, + // so we guess by doubling the buffer size. + dwInfoSize *= 2; + pHandleInfo = HeapAlloc(GetProcessHeap(), + HEAP_ZERO_MEMORY, + dwInfoSize); + + if (pHandleInfo == NULL) { + PyErr_NoMemory(); + error = TRUE; + goto cleanup; + } + } while ((status = __NtQuerySystemInformation( + SystemExtendedHandleInformation, + pHandleInfo, + dwInfoSize, + &dwRet)) == STATUS_INFO_LENGTH_MISMATCH); + + // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH + if (!NT_SUCCESS(status)) { + PyErr_SetFromWindowsErr(HRESULT_FROM_NT(status)); + error = TRUE; + goto cleanup; + } + + for (i = 0; i < pHandleInfo->NumberOfHandles; i++) { + hHandle = &pHandleInfo->Handles[i]; + + // Check if this hHandle belongs to the PID the user specified. + if (hHandle->UniqueProcessId != (HANDLE)dwPid || + hHandle->ObjectTypeIndex != HANDLE_TYPE_FILE) + goto loop_cleanup; + + if (!DuplicateHandle(hProcess, + hHandle->HandleValue, + GetCurrentProcess(), + &hFile, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) + { + /* + printf("[%d] DuplicateHandle (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hMap == NULL) { + /* + printf("[%d] CreateFileMapping (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + pMem = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 1); + + if (pMem == NULL) { + /* + printf("[%d] MapViewOfFile (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + dwSize = GetMappedFileName(GetCurrentProcess(), pMem, pszFilename, MAX_PATH); + if (dwSize == 0) { + /* + printf("[%d] GetMappedFileName (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + goto loop_cleanup; + } + + pszFilename[dwSize] = '\0'; + /* + printf("[%d] Filename (%#x) %#d bytes: %S\n", + dwPid, + hHandle->HandleValue, + dwSize, + pszFilename); + */ + + pyFilePath = PyUnicode_FromWideChar(pszFilename, dwSize); + if (pyFilePath == NULL) { + /* + printf("[%d] PyUnicode_FromStringAndSize (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + + if (PyList_Append(pyListFiles, pyFilePath)) { + /* + printf("[%d] PyList_Append (%#x): %#x \n", + dwPid, + hHandle->HandleValue, + GetLastError()); + */ + error = TRUE; + goto loop_cleanup; + } + +loop_cleanup: + Py_XDECREF(pyFilePath); + pyFilePath = NULL; + + if (pMem != NULL) + UnmapViewOfFile(pMem); + pMem = NULL; + + if (hMap != NULL) + CloseHandle(hMap); + hMap = NULL; + + if (hFile != NULL) + CloseHandle(hFile); + hFile = NULL; + + dwSize = 0; + } + +cleanup: + if (pMem != NULL) + UnmapViewOfFile(pMem); + pMem = NULL; + + if (hMap != NULL) + CloseHandle(hMap); + hMap = NULL; + + if (hFile != NULL) + CloseHandle(hFile); + hFile = NULL; + + if (pHandleInfo != NULL) + HeapFree(GetProcessHeap(), 0, pHandleInfo); + pHandleInfo = NULL; + + if (error) { + Py_XDECREF(pyListFiles); + pyListFiles = NULL; + } + + return pyListFiles; +} diff --git a/python/psutil/psutil/arch/windows/process_handles.h b/python/psutil/psutil/arch/windows/process_handles.h new file mode 100644 index 0000000000..4cf4023ecc --- /dev/null +++ b/python/psutil/psutil/arch/windows/process_handles.h @@ -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. + */ + +#ifndef __PROCESS_HANDLES_H__ +#define __PROCESS_HANDLES_H__ + +#ifndef UNICODE +#define UNICODE +#endif + +#include <Python.h> +#include <stdio.h> +#include <windows.h> +#include <strsafe.h> +#include <winternl.h> +#include <psapi.h> + + +#ifndef NT_SUCCESS +#define NT_SUCCESS(x) ((x) >= 0) +#endif + +#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004 +#define ObjectBasicInformation 0 +#define ObjectNameInformation 1 +#define ObjectTypeInformation 2 +#define HANDLE_TYPE_FILE 28 +#define NTQO_TIMEOUT 100 + +typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)( + ULONG SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength +); + +typedef NTSTATUS (NTAPI *_NtQueryObject)( + HANDLE ObjectHandle, + ULONG ObjectInformationClass, + PVOID ObjectInformation, + ULONG ObjectInformationLength, + PULONG ReturnLength +); + +// Undocumented FILE_INFORMATION_CLASS: FileNameInformation +static const SYSTEM_INFORMATION_CLASS SystemExtendedHandleInformation = (SYSTEM_INFORMATION_CLASS)64; + +typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX +{ + PVOID Object; + HANDLE UniqueProcessId; + HANDLE HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX; + +typedef struct _SYSTEM_HANDLE_INFORMATION_EX +{ + ULONG_PTR NumberOfHandles; + ULONG_PTR Reserved; + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + DontUseThisType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS +} POOL_TYPE, *PPOOL_TYPE; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING Name; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccess; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + USHORT MaintainTypeList; + POOL_TYPE PoolType; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName); +VOID psutil_get_open_files_init(BOOL threaded); +PyObject* psutil_get_open_files(long pid, HANDLE processHandle); +PyObject* psutil_get_open_files_ntqueryobject(long dwPid, HANDLE hProcess); +PyObject* psutil_get_open_files_getmappedfilename(long dwPid, HANDLE hProcess); +DWORD psutil_NtQueryObject(void); +void psutil_NtQueryObjectThread(void); + +#endif // __PROCESS_HANDLES_H__ diff --git a/python/psutil/psutil/arch/windows/process_info.c b/python/psutil/psutil/arch/windows/process_info.c new file mode 100644 index 0000000000..a59cce47a2 --- /dev/null +++ b/python/psutil/psutil/arch/windows/process_info.c @@ -0,0 +1,435 @@ +/* + * 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_windows module methods. + */ + +#include <Python.h> +#include <windows.h> +#include <Psapi.h> +#include <tlhelp32.h> + +#include "security.h" +#include "process_info.h" +#include "ntextapi.h" +#include "../../_psutil_common.h" + + +/* + * A wrapper around OpenProcess setting NSP exception if process + * no longer exists. + * "pid" is the process pid, "dwDesiredAccess" is the first argument + * exptected by OpenProcess. + * Return a process handle or NULL. + */ +HANDLE +psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess) +{ + HANDLE hProcess; + DWORD processExitCode = 0; + + if (pid == 0) { + // otherwise we'd get NoSuchProcess + return AccessDenied(); + } + + hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) + NoSuchProcess(); + else + PyErr_SetFromWindowsErr(0); + return NULL; + } + + // make sure the process is running + GetExitCodeProcess(hProcess, &processExitCode); + if (processExitCode == 0) { + NoSuchProcess(); + CloseHandle(hProcess); + return NULL; + } + return hProcess; +} + + +/* + * Same as psutil_handle_from_pid_waccess but implicitly uses + * PROCESS_QUERY_INFORMATION | PROCESS_VM_READ as dwDesiredAccess + * parameter for OpenProcess. + */ +HANDLE +psutil_handle_from_pid(DWORD pid) { + DWORD dwDesiredAccess = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + return psutil_handle_from_pid_waccess(pid, dwDesiredAccess); +} + + +// fetch the PEB base address from NtQueryInformationProcess() +PVOID +psutil_get_peb_address(HANDLE ProcessHandle) +{ + _NtQueryInformationProcess NtQueryInformationProcess = + (_NtQueryInformationProcess)GetProcAddress( + GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); + PROCESS_BASIC_INFORMATION pbi; + + NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL); + return pbi.PebBaseAddress; +} + + +DWORD * +psutil_get_pids(DWORD *numberOfReturnedPIDs) { + // Win32 SDK says the only way to know if our process array + // wasn't large enough is to check the returned size and make + // sure that it doesn't match the size of the array. + // If it does we allocate a larger array and try again + + // Stores the actual array + DWORD *procArray = NULL; + DWORD procArrayByteSz; + int procArraySz = 0; + + // Stores the byte size of the returned array from enumprocesses + DWORD enumReturnSz = 0; + + do { + procArraySz += 1024; + free(procArray); + procArrayByteSz = procArraySz * sizeof(DWORD); + procArray = malloc(procArrayByteSz); + if (procArray == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (! EnumProcesses(procArray, procArrayByteSz, &enumReturnSz)) { + free(procArray); + PyErr_SetFromWindowsErr(0); + return NULL; + } + } while (enumReturnSz == procArraySz * sizeof(DWORD)); + + // The number of elements is the returned size / size of each element + *numberOfReturnedPIDs = enumReturnSz / sizeof(DWORD); + + return procArray; +} + + +int +psutil_pid_is_running(DWORD pid) +{ + HANDLE hProcess; + DWORD exitCode; + + // Special case for PID 0 System Idle Process + if (pid == 0) + return 1; + if (pid < 0) + return 0; + + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, pid); + if (NULL == hProcess) { + // invalid parameter is no such process + if (GetLastError() == ERROR_INVALID_PARAMETER) { + CloseHandle(hProcess); + return 0; + } + + // access denied obviously means there's a process to deny access to... + if (GetLastError() == ERROR_ACCESS_DENIED) { + CloseHandle(hProcess); + return 1; + } + + CloseHandle(hProcess); + PyErr_SetFromWindowsErr(0); + return -1; + } + + if (GetExitCodeProcess(hProcess, &exitCode)) { + CloseHandle(hProcess); + return (exitCode == STILL_ACTIVE); + } + + // access denied means there's a process there so we'll assume + // it's running + if (GetLastError() == ERROR_ACCESS_DENIED) { + CloseHandle(hProcess); + return 1; + } + + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return -1; +} + + +int +psutil_pid_in_proclist(DWORD pid) +{ + DWORD *proclist = NULL; + DWORD numberOfReturnedPIDs; + DWORD i; + + proclist = psutil_get_pids(&numberOfReturnedPIDs); + if (proclist == NULL) + return -1; + for (i = 0; i < numberOfReturnedPIDs; i++) { + if (pid == proclist[i]) { + free(proclist); + return 1; + } + } + + free(proclist); + return 0; +} + + +// Check exit code from a process handle. Return FALSE on an error also +// XXX - not used anymore +int +handlep_is_running(HANDLE hProcess) +{ + DWORD dwCode; + + if (NULL == hProcess) + return 0; + if (GetExitCodeProcess(hProcess, &dwCode)) { + if (dwCode == STILL_ACTIVE) + return 1; + } + return 0; +} + + +/* + * returns a Python list representing the arguments for the process + * with given pid or NULL on error. + */ +PyObject * +psutil_get_arg_list(long pid) +{ + int nArgs, i; + LPWSTR *szArglist = NULL; + HANDLE hProcess = NULL; + PVOID pebAddress; + PVOID rtlUserProcParamsAddress; + UNICODE_STRING commandLine; + WCHAR *commandLineContents = NULL; + PyObject *arg = NULL; + PyObject *arg_from_wchar = NULL; + PyObject *argList = NULL; + + hProcess = psutil_handle_from_pid(pid); + if (hProcess == NULL) + return NULL; + pebAddress = psutil_get_peb_address(hProcess); + + // get the address of ProcessParameters +#ifdef _WIN64 + if (!ReadProcessMemory(hProcess, (PCHAR)pebAddress + 32, + &rtlUserProcParamsAddress, sizeof(PVOID), NULL)) +#else + if (!ReadProcessMemory(hProcess, (PCHAR)pebAddress + 0x10, + &rtlUserProcParamsAddress, sizeof(PVOID), NULL)) +#endif + { + ////printf("Could not read the address of ProcessParameters!\n"); + PyErr_SetFromWindowsErr(0); + goto error; + } + + // read the CommandLine UNICODE_STRING structure +#ifdef _WIN64 + if (!ReadProcessMemory(hProcess, (PCHAR)rtlUserProcParamsAddress + 112, + &commandLine, sizeof(commandLine), NULL)) +#else + if (!ReadProcessMemory(hProcess, (PCHAR)rtlUserProcParamsAddress + 0x40, + &commandLine, sizeof(commandLine), NULL)) +#endif + { + PyErr_SetFromWindowsErr(0); + goto error; + } + + + // allocate memory to hold the command line + commandLineContents = (WCHAR *)malloc(commandLine.Length + 1); + if (commandLineContents == NULL) { + PyErr_NoMemory(); + goto error; + } + + // read the command line + if (!ReadProcessMemory(hProcess, commandLine.Buffer, + commandLineContents, commandLine.Length, NULL)) + { + PyErr_SetFromWindowsErr(0); + goto error; + } + + // Null-terminate the string to prevent wcslen from returning + // incorrect length the length specifier is in characters, but + // commandLine.Length is in bytes. + commandLineContents[(commandLine.Length / sizeof(WCHAR))] = '\0'; + + // attempt tp parse the command line using Win32 API, fall back + // on string cmdline version otherwise + szArglist = CommandLineToArgvW(commandLineContents, &nArgs); + if (NULL == szArglist) { + // failed to parse arglist + // encode as a UTF8 Python string object from WCHAR string + arg_from_wchar = PyUnicode_FromWideChar(commandLineContents, + commandLine.Length / 2); + if (arg_from_wchar == NULL) + goto error; +#if PY_MAJOR_VERSION >= 3 + argList = Py_BuildValue("N", PyUnicode_AsUTF8String(arg_from_wchar)); +#else + argList = Py_BuildValue("N", PyUnicode_FromObject(arg_from_wchar)); +#endif + if (!argList) + goto error; + } + else { + // arglist parsed as array of UNICODE_STRING, so convert each to + // Python string object and add to arg list + argList = Py_BuildValue("[]"); + if (argList == NULL) + goto error; + for (i = 0; i < nArgs; i++) { + arg_from_wchar = NULL; + arg = NULL; + arg_from_wchar = PyUnicode_FromWideChar(szArglist[i], + wcslen(szArglist[i])); + if (arg_from_wchar == NULL) + goto error; +#if PY_MAJOR_VERSION >= 3 + arg = PyUnicode_FromObject(arg_from_wchar); +#else + arg = PyUnicode_AsUTF8String(arg_from_wchar); +#endif + if (arg == NULL) + goto error; + Py_XDECREF(arg_from_wchar); + if (PyList_Append(argList, arg)) + goto error; + Py_XDECREF(arg); + } + } + + if (szArglist != NULL) + LocalFree(szArglist); + free(commandLineContents); + CloseHandle(hProcess); + return argList; + +error: + Py_XDECREF(arg); + Py_XDECREF(arg_from_wchar); + Py_XDECREF(argList); + if (hProcess != NULL) + CloseHandle(hProcess); + if (commandLineContents != NULL) + free(commandLineContents); + if (szArglist != NULL) + LocalFree(szArglist); + return NULL; +} + + +#define PH_FIRST_PROCESS(Processes) ((PSYSTEM_PROCESS_INFORMATION)(Processes)) +#define PH_NEXT_PROCESS(Process) ( \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ + (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : \ + NULL) + +const int STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; +const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L; + +/* + * Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure + * fills the structure with various process information by using + * NtQuerySystemInformation. + * We use this as a fallback when faster functions fail with access + * denied. This is slower because it iterates over all processes. + * On success return 1, else 0 with Python exception already set. + */ +int +psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, + PVOID *retBuffer) +{ + static ULONG initialBufferSize = 0x4000; + NTSTATUS status; + PVOID buffer; + ULONG bufferSize; + PSYSTEM_PROCESS_INFORMATION process; + + // get NtQuerySystemInformation + typedef DWORD (_stdcall * NTQSI_PROC) (int, PVOID, ULONG, PULONG); + NTQSI_PROC NtQuerySystemInformation; + HINSTANCE hNtDll; + hNtDll = LoadLibrary(TEXT("ntdll.dll")); + NtQuerySystemInformation = (NTQSI_PROC)GetProcAddress( + hNtDll, "NtQuerySystemInformation"); + + bufferSize = initialBufferSize; + buffer = malloc(bufferSize); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + + while (TRUE) { + status = NtQuerySystemInformation(SystemProcessInformation, buffer, + bufferSize, &bufferSize); + + if (status == STATUS_BUFFER_TOO_SMALL || + status == STATUS_INFO_LENGTH_MISMATCH) + { + free(buffer); + buffer = malloc(bufferSize); + if (buffer == NULL) { + PyErr_NoMemory(); + goto error; + } + } + else { + break; + } + } + + if (status != 0) { + PyErr_Format(PyExc_RuntimeError, "NtQuerySystemInformation() failed"); + goto error; + } + + if (bufferSize <= 0x20000) + initialBufferSize = bufferSize; + + process = PH_FIRST_PROCESS(buffer); + do { + if (process->UniqueProcessId == (HANDLE)pid) { + *retProcess = process; + *retBuffer = buffer; + return 1; + } + } while ( (process = PH_NEXT_PROCESS(process)) ); + + NoSuchProcess(); + goto error; + +error: + FreeLibrary(hNtDll); + if (buffer != NULL) + free(buffer); + return 0; +} diff --git a/python/psutil/psutil/arch/windows/process_info.h b/python/psutil/psutil/arch/windows/process_info.h new file mode 100644 index 0000000000..a44c4aced0 --- /dev/null +++ b/python/psutil/psutil/arch/windows/process_info.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#if !defined(__PROCESS_INFO_H) +#define __PROCESS_INFO_H + +#include <Python.h> +#include <windows.h> +#include "security.h" +#include "ntextapi.h" + +DWORD* psutil_get_pids(DWORD *numberOfReturnedPIDs); +HANDLE psutil_handle_from_pid(DWORD pid); +HANDLE psutil_handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess); +int psutil_handlep_is_running(HANDLE hProcess); +int psutil_pid_in_proclist(DWORD pid); +int psutil_pid_is_running(DWORD pid); +PVOID psutil_get_peb_address(HANDLE ProcessHandle); +PyObject* psutil_get_arg_list(long pid); +int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, + PVOID *retBuffer); + +#endif diff --git a/python/psutil/psutil/arch/windows/security.c b/python/psutil/psutil/arch/windows/security.c new file mode 100644 index 0000000000..3aabffd0cb --- /dev/null +++ b/python/psutil/psutil/arch/windows/security.c @@ -0,0 +1,228 @@ +/* + * 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. + * + * Security related functions for Windows platform (Set privileges such as + * SeDebug), as well as security helper functions. + */ + +#include <windows.h> +#include <Python.h> + + +/* + * Convert a process handle to a process token handle. + */ +HANDLE +psutil_token_from_handle(HANDLE hProcess) { + HANDLE hToken = NULL; + + if (! OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) + return PyErr_SetFromWindowsErr(0); + return hToken; +} + + +/* + * http://www.ddj.com/windows/184405986 + * + * There's a way to determine whether we're running under the Local System + * account. However (you guessed it), we have to call more Win32 functions to + * determine this. Backing up through the code listing, we need to make another + * call to GetTokenInformation, but instead of passing through the TOKEN_USER + * constant, we pass through the TOKEN_PRIVILEGES constant. This value returns + * an array of privileges that the account has in the environment. Iterating + * through the array, we call the function LookupPrivilegeName looking for the + * string SeTcbPrivilege. If the function returns this string, then this + * account has Local System privileges + */ +int +psutil_has_system_privilege(HANDLE hProcess) { + DWORD i; + DWORD dwSize = 0; + DWORD dwRetval = 0; + TCHAR privName[256]; + DWORD dwNameSize = 256; + // PTOKEN_PRIVILEGES tp = NULL; + BYTE *pBuffer = NULL; + TOKEN_PRIVILEGES *tp = NULL; + HANDLE hToken = psutil_token_from_handle(hProcess); + + if (NULL == hToken) + return -1; + // call GetTokenInformation first to get the buffer size + if (! GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwSize)) { + dwRetval = GetLastError(); + // if it failed for a reason other than the buffer, bail out + if (dwRetval != ERROR_INSUFFICIENT_BUFFER ) { + PyErr_SetFromWindowsErr(dwRetval); + return 0; + } + } + + // allocate buffer and call GetTokenInformation again + // tp = (PTOKEN_PRIVILEGES) GlobalAlloc(GPTR, dwSize); + pBuffer = (BYTE *) malloc(dwSize); + if (pBuffer == NULL) { + PyErr_NoMemory(); + return -1; + } + + if (! GetTokenInformation(hToken, TokenPrivileges, pBuffer, + dwSize, &dwSize)) + { + PyErr_SetFromWindowsErr(0); + free(pBuffer); + return -1; + } + + // convert the BYTE buffer to a TOKEN_PRIVILEGES struct pointer + tp = (TOKEN_PRIVILEGES *)pBuffer; + + // check all the privileges looking for SeTcbPrivilege + for (i = 0; i < tp->PrivilegeCount; i++) { + // reset the buffer contents and the buffer size + strcpy(privName, ""); + dwNameSize = sizeof(privName) / sizeof(TCHAR); + if (! LookupPrivilegeName(NULL, + &tp->Privileges[i].Luid, + (LPTSTR)privName, + &dwNameSize)) + { + PyErr_SetFromWindowsErr(0); + free(pBuffer); + return -1; + } + + // if we find the SeTcbPrivilege then it's a LocalSystem process + if (! lstrcmpi(privName, TEXT("SeTcbPrivilege"))) { + free(pBuffer); + return 1; + } + } + + free(pBuffer); + return 0; +} + + +BOOL +psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) +{ + TOKEN_PRIVILEGES tp; + LUID luid; + TOKEN_PRIVILEGES tpPrevious; + DWORD cbPrevious = sizeof(TOKEN_PRIVILEGES); + + if (!LookupPrivilegeValue( NULL, Privilege, &luid )) return FALSE; + + // first pass. get current privilege setting + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = 0; + + AdjustTokenPrivileges( + hToken, + FALSE, + &tp, + sizeof(TOKEN_PRIVILEGES), + &tpPrevious, + &cbPrevious + ); + + if (GetLastError() != ERROR_SUCCESS) return FALSE; + + // second pass. set privilege based on previous setting + tpPrevious.PrivilegeCount = 1; + tpPrevious.Privileges[0].Luid = luid; + + if (bEnablePrivilege) + tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED); + else + tpPrevious.Privileges[0].Attributes ^= + (SE_PRIVILEGE_ENABLED & tpPrevious.Privileges[0].Attributes); + + AdjustTokenPrivileges( + hToken, + FALSE, + &tpPrevious, + cbPrevious, + NULL, + NULL + ); + + if (GetLastError() != ERROR_SUCCESS) return FALSE; + + return TRUE; +} + + +int +psutil_set_se_debug() +{ + HANDLE hToken; + if (! OpenThreadToken(GetCurrentThread(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + FALSE, + &hToken) + ) { + if (GetLastError() == ERROR_NO_TOKEN) { + if (!ImpersonateSelf(SecurityImpersonation)) { + CloseHandle(hToken); + return 0; + } + if (!OpenThreadToken(GetCurrentThread(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + FALSE, + &hToken) + ) { + RevertToSelf(); + CloseHandle(hToken); + return 0; + } + } + } + + // enable SeDebugPrivilege (open any process) + if (! psutil_set_privilege(hToken, SE_DEBUG_NAME, TRUE)) { + RevertToSelf(); + CloseHandle(hToken); + return 0; + } + + RevertToSelf(); + CloseHandle(hToken); + return 1; +} + + +int +psutil_unset_se_debug() +{ + HANDLE hToken; + if (! OpenThreadToken(GetCurrentThread(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + FALSE, + &hToken) + ) { + if (GetLastError() == ERROR_NO_TOKEN) { + if (! ImpersonateSelf(SecurityImpersonation)) + return 0; + if (!OpenThreadToken(GetCurrentThread(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + FALSE, + &hToken)) + { + return 0; + } + } + } + + // now disable SeDebug + if (! psutil_set_privilege(hToken, SE_DEBUG_NAME, FALSE)) + return 0; + + CloseHandle(hToken); + return 1; +} diff --git a/python/psutil/psutil/arch/windows/security.h b/python/psutil/psutil/arch/windows/security.h new file mode 100644 index 0000000000..aa8a22ad1a --- /dev/null +++ b/python/psutil/psutil/arch/windows/security.h @@ -0,0 +1,17 @@ +/* + * 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. + * + * Security related functions for Windows platform (Set privileges such as + * SeDebug), as well as security helper functions. + */ + +#include <windows.h> + +BOOL psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege); +HANDLE psutil_token_from_handle(HANDLE hProcess); +int psutil_has_system_privilege(HANDLE hProcess); +int psutil_set_se_debug(); +int psutil_unset_se_debug(); + diff --git a/python/psutil/setup.cfg b/python/psutil/setup.cfg new file mode 100644 index 0000000000..861a9f5542 --- /dev/null +++ b/python/psutil/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/python/psutil/setup.py b/python/psutil/setup.py new file mode 100644 index 0000000000..4c42548ef3 --- /dev/null +++ b/python/psutil/setup.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python + +# 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. + +"""psutil is a cross-platform library for retrieving information on +running processes and system utilization (CPU, memory, disks, network) +in Python. +""" + +import os +import sys +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension + + +HERE = os.path.abspath(os.path.dirname(__file__)) + + +def get_version(): + INIT = os.path.join(HERE, 'psutil/__init__.py') + with open(INIT, 'r') as f: + for line in f: + if line.startswith('__version__'): + ret = eval(line.strip().split(' = ')[1]) + assert ret.count('.') == 2, ret + for num in ret.split('.'): + assert num.isdigit(), ret + return ret + else: + raise ValueError("couldn't find version string") + + +def get_description(): + README = os.path.join(HERE, 'README.rst') + with open(README, 'r') as f: + return f.read() + + +VERSION = get_version() +VERSION_MACRO = ('PSUTIL_VERSION', int(VERSION.replace('.', ''))) + + +# POSIX +if os.name == 'posix': + libraries = [] + if sys.platform.startswith("sunos"): + libraries.append('socket') + + posix_extension = Extension( + 'psutil._psutil_posix', + sources=['psutil/_psutil_posix.c'], + libraries=libraries, + ) +# Windows +if sys.platform.startswith("win32"): + + def get_winver(): + maj, min = sys.getwindowsversion()[0:2] + return '0x0%s' % ((maj * 100) + min) + + extensions = [Extension( + 'psutil._psutil_windows', + sources=[ + 'psutil/_psutil_windows.c', + 'psutil/_psutil_common.c', + 'psutil/arch/windows/process_info.c', + 'psutil/arch/windows/process_handles.c', + 'psutil/arch/windows/security.c', + 'psutil/arch/windows/inet_ntop.c', + ], + define_macros=[ + VERSION_MACRO, + # be nice to mingw, see: + # http://www.mingw.org/wiki/Use_more_recent_defined_functions + ('_WIN32_WINNT', get_winver()), + ('_AVAIL_WINVER_', get_winver()), + ('_CRT_SECURE_NO_WARNINGS', None), + # see: https://github.com/giampaolo/psutil/issues/348 + ('PSAPI_VERSION', 1), + ], + libraries=[ + "psapi", "kernel32", "advapi32", "shell32", "netapi32", "iphlpapi", + "wtsapi32", "ws2_32", + ], + # extra_compile_args=["/Z7"], + # extra_link_args=["/DEBUG"] + )] +# OS X +elif sys.platform.startswith("darwin"): + extensions = [Extension( + 'psutil._psutil_osx', + sources=[ + 'psutil/_psutil_osx.c', + 'psutil/_psutil_common.c', + 'psutil/arch/osx/process_info.c' + ], + define_macros=[VERSION_MACRO], + extra_link_args=[ + '-framework', 'CoreFoundation', '-framework', 'IOKit' + ], + ), + posix_extension, + ] +# FreeBSD +elif sys.platform.startswith("freebsd"): + extensions = [Extension( + 'psutil._psutil_bsd', + sources=[ + 'psutil/_psutil_bsd.c', + 'psutil/_psutil_common.c', + 'psutil/arch/bsd/process_info.c' + ], + define_macros=[VERSION_MACRO], + libraries=["devstat"]), + posix_extension, + ] +# Linux +elif sys.platform.startswith("linux"): + extensions = [Extension( + 'psutil._psutil_linux', + sources=['psutil/_psutil_linux.c'], + define_macros=[VERSION_MACRO]), + posix_extension, + ] +# Solaris +elif sys.platform.lower().startswith('sunos'): + extensions = [Extension( + 'psutil._psutil_sunos', + sources=['psutil/_psutil_sunos.c'], + define_macros=[VERSION_MACRO], + libraries=['kstat', 'nsl', 'socket']), + posix_extension, + ] +else: + sys.exit('platform %s is not supported' % sys.platform) + + +def main(): + setup_args = dict( + name='psutil', + version=VERSION, + description=__doc__.replace('\n', '').strip(), + long_description=get_description(), + 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', + ], + author='Giampaolo Rodola', + author_email='g.rodola <at> gmail <dot> com', + url='https://github.com/giampaolo/psutil', + platforms='Platform Independent', + license='BSD', + packages=['psutil'], + # see: python setup.py register --list-classifiers + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Environment :: Win32 (MS Windows)', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows :: Windows NT/2000', + 'Operating System :: Microsoft', + 'Operating System :: OS Independent', + 'Operating System :: POSIX :: BSD :: FreeBSD', + 'Operating System :: POSIX :: Linux', + 'Operating System :: POSIX :: SunOS/Solaris', + 'Operating System :: POSIX', + 'Programming Language :: C', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.0', + 'Programming Language :: Python :: 3.1', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Libraries', + 'Topic :: System :: Benchmark', + 'Topic :: System :: Hardware', + 'Topic :: System :: Monitoring', + 'Topic :: System :: Networking :: Monitoring', + 'Topic :: System :: Networking', + 'Topic :: System :: Systems Administration', + 'Topic :: Utilities', + ], + ) + if extensions is not None: + setup_args["ext_modules"] = extensions + setup(**setup_args) + +if __name__ == '__main__': + main() diff --git a/python/psutil/test/README.rst b/python/psutil/test/README.rst new file mode 100644 index 0000000000..3f2a468efd --- /dev/null +++ b/python/psutil/test/README.rst @@ -0,0 +1,21 @@ +- The recommended way to run tests (also on Windows) is to cd into parent + directory and run ``make test`` + +- Dependencies for running tests: + - python 2.6: ipaddress, mock, unittest2 + - python 2.7: ipaddress, mock + - python 3.2: ipaddress, mock + - python 3.3: ipaddress + - python >= 3.4: no deps required + +- The main test script is ``test_psutil.py``, which also imports platform-specific + ``_*.py`` scripts (which should be ignored). + +- ``test_memory_leaks.py`` looks for memory leaks into C extension modules and must + be run separately with ``make test-memleaks``. + +- To run tests on all supported Python version install tox (pip install tox) + then run ``tox``. + +- Every time a commit is pushed tests are automatically run on Travis: + https://travis-ci.org/giampaolo/psutil/ diff --git a/python/psutil/test/_bsd.py b/python/psutil/test/_bsd.py new file mode 100644 index 0000000000..e4a3225d20 --- /dev/null +++ b/python/psutil/test/_bsd.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python + +# 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. + +# TODO: add test for comparing connections with 'sockstat' cmd + +"""BSD specific tests. These are implicitly run by test_psutil.py.""" + +import os +import subprocess +import sys +import time + +import psutil + +from psutil._compat import PY3 +from test_psutil import (TOLERANCE, BSD, sh, get_test_subprocess, which, + retry_before_failing, reap_children, unittest) + + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") +if os.getuid() == 0: # muse requires root privileges + MUSE_AVAILABLE = which('muse') +else: + MUSE_AVAILABLE = False + + +def sysctl(cmdline): + """Expects a sysctl command with an argument and parse the result + returning only the value of interest. + """ + result = sh("sysctl " + cmdline) + result = result[result.find(": ") + 2:] + try: + return int(result) + except ValueError: + return result + + +def muse(field): + """Thin wrapper around 'muse' cmdline utility.""" + out = sh('muse') + for line in out.split('\n'): + if line.startswith(field): + break + else: + raise ValueError("line not found") + return int(line.split()[1]) + + +@unittest.skipUnless(BSD, "not a BSD system") +class BSDSpecificTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.pid = get_test_subprocess().pid + + @classmethod + def tearDownClass(cls): + reap_children() + + def test_boot_time(self): + s = sysctl('sysctl kern.boottime') + s = s[s.find(" sec = ") + 7:] + s = s[:s.find(',')] + btime = int(s) + self.assertEqual(btime, psutil.boot_time()) + + def test_process_create_time(self): + cmdline = "ps -o lstart -p %s" % self.pid + p = subprocess.Popen(cmdline, shell=1, stdout=subprocess.PIPE) + output = p.communicate()[0] + if PY3: + output = str(output, sys.stdout.encoding) + start_ps = output.replace('STARTED', '').strip() + start_psutil = psutil.Process(self.pid).create_time() + start_psutil = time.strftime("%a %b %e %H:%M:%S %Y", + time.localtime(start_psutil)) + self.assertEqual(start_ps, start_psutil) + + def test_disks(self): + # test psutil.disk_usage() and psutil.disk_partitions() + # against "df -a" + def df(path): + out = sh('df -k "%s"' % path).strip() + lines = out.split('\n') + lines.pop(0) + line = lines.pop(0) + dev, total, used, free = line.split()[:4] + if dev == 'none': + dev = '' + total = int(total) * 1024 + used = int(used) * 1024 + free = int(free) * 1024 + return dev, total, used, free + + for part in psutil.disk_partitions(all=False): + usage = psutil.disk_usage(part.mountpoint) + dev, total, used, free = df(part.mountpoint) + self.assertEqual(part.device, dev) + self.assertEqual(usage.total, total) + # 10 MB tollerance + if abs(usage.free - free) > 10 * 1024 * 1024: + self.fail("psutil=%s, df=%s" % (usage.free, free)) + if abs(usage.used - used) > 10 * 1024 * 1024: + self.fail("psutil=%s, df=%s" % (usage.used, used)) + + @retry_before_failing() + def test_memory_maps(self): + out = sh('procstat -v %s' % self.pid) + maps = psutil.Process(self.pid).memory_maps(grouped=False) + lines = out.split('\n')[1:] + while lines: + line = lines.pop() + fields = line.split() + _, start, stop, perms, res = fields[:5] + map = maps.pop() + self.assertEqual("%s-%s" % (start, stop), map.addr) + self.assertEqual(int(res), map.rss) + if not map.path.startswith('['): + self.assertEqual(fields[10], map.path) + + def test_exe(self): + out = sh('procstat -b %s' % self.pid) + self.assertEqual(psutil.Process(self.pid).exe(), + out.split('\n')[1].split()[-1]) + + def test_cmdline(self): + out = sh('procstat -c %s' % self.pid) + self.assertEqual(' '.join(psutil.Process(self.pid).cmdline()), + ' '.join(out.split('\n')[1].split()[2:])) + + def test_uids_gids(self): + out = sh('procstat -s %s' % self.pid) + euid, ruid, suid, egid, rgid, sgid = out.split('\n')[1].split()[2:8] + p = psutil.Process(self.pid) + uids = p.uids() + gids = p.gids() + self.assertEqual(uids.real, int(ruid)) + self.assertEqual(uids.effective, int(euid)) + self.assertEqual(uids.saved, int(suid)) + self.assertEqual(gids.real, int(rgid)) + self.assertEqual(gids.effective, int(egid)) + self.assertEqual(gids.saved, int(sgid)) + + # --- virtual_memory(); tests against sysctl + + def test_vmem_total(self): + syst = sysctl("sysctl vm.stats.vm.v_page_count") * PAGESIZE + self.assertEqual(psutil.virtual_memory().total, syst) + + @retry_before_failing() + def test_vmem_active(self): + syst = sysctl("vm.stats.vm.v_active_count") * PAGESIZE + self.assertAlmostEqual(psutil.virtual_memory().active, syst, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_inactive(self): + syst = sysctl("vm.stats.vm.v_inactive_count") * PAGESIZE + self.assertAlmostEqual(psutil.virtual_memory().inactive, syst, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_wired(self): + syst = sysctl("vm.stats.vm.v_wire_count") * PAGESIZE + self.assertAlmostEqual(psutil.virtual_memory().wired, syst, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_cached(self): + syst = sysctl("vm.stats.vm.v_cache_count") * PAGESIZE + self.assertAlmostEqual(psutil.virtual_memory().cached, syst, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_free(self): + syst = sysctl("vm.stats.vm.v_free_count") * PAGESIZE + self.assertAlmostEqual(psutil.virtual_memory().free, syst, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_buffers(self): + syst = sysctl("vfs.bufspace") + self.assertAlmostEqual(psutil.virtual_memory().buffers, syst, + delta=TOLERANCE) + + def test_cpu_count_logical(self): + syst = sysctl("hw.ncpu") + self.assertEqual(psutil.cpu_count(logical=True), syst) + + # --- virtual_memory(); tests against muse + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + def test_total(self): + num = muse('Total') + self.assertEqual(psutil.virtual_memory().total, num) + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + @retry_before_failing() + def test_active(self): + num = muse('Active') + self.assertAlmostEqual(psutil.virtual_memory().active, num, + delta=TOLERANCE) + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + @retry_before_failing() + def test_inactive(self): + num = muse('Inactive') + self.assertAlmostEqual(psutil.virtual_memory().inactive, num, + delta=TOLERANCE) + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + @retry_before_failing() + def test_wired(self): + num = muse('Wired') + self.assertAlmostEqual(psutil.virtual_memory().wired, num, + delta=TOLERANCE) + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + @retry_before_failing() + def test_cached(self): + num = muse('Cache') + self.assertAlmostEqual(psutil.virtual_memory().cached, num, + delta=TOLERANCE) + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + @retry_before_failing() + def test_free(self): + num = muse('Free') + self.assertAlmostEqual(psutil.virtual_memory().free, num, + delta=TOLERANCE) + + @unittest.skipUnless(MUSE_AVAILABLE, "muse cmdline tool is not available") + @retry_before_failing() + def test_buffers(self): + num = muse('Buffer') + self.assertAlmostEqual(psutil.virtual_memory().buffers, num, + delta=TOLERANCE) + + +def main(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(BSDSpecificTestCase)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/_linux.py b/python/psutil/test/_linux.py new file mode 100644 index 0000000000..c1927ea8b2 --- /dev/null +++ b/python/psutil/test/_linux.py @@ -0,0 +1,473 @@ +#!/usr/bin/env python + +# 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. + +"""Linux specific tests. These are implicitly run by test_psutil.py.""" + +from __future__ import division +import contextlib +import errno +import fcntl +import io +import os +import pprint +import re +import socket +import struct +import sys +import tempfile +import time +import warnings + +try: + from unittest import mock # py3 +except ImportError: + import mock # requires "pip install mock" + +from test_psutil import POSIX, TOLERANCE, TRAVIS, LINUX +from test_psutil import (skip_on_not_implemented, sh, get_test_subprocess, + retry_before_failing, get_kernel_version, unittest, + which, call_until) + +import psutil +import psutil._pslinux +from psutil._compat import PY3, u + + +SIOCGIFADDR = 0x8915 +SIOCGIFCONF = 0x8912 +SIOCGIFHWADDR = 0x8927 + + +def get_ipv4_address(ifname): + ifname = ifname[:15] + if PY3: + ifname = bytes(ifname, 'ascii') + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + return socket.inet_ntoa( + fcntl.ioctl(s.fileno(), + SIOCGIFADDR, + struct.pack('256s', ifname))[20:24]) + + +def get_mac_address(ifname): + ifname = ifname[:15] + if PY3: + ifname = bytes(ifname, 'ascii') + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + with contextlib.closing(s): + info = fcntl.ioctl( + s.fileno(), SIOCGIFHWADDR, struct.pack('256s', ifname)) + if PY3: + def ord(x): + return x + else: + import __builtin__ + ord = __builtin__.ord + return ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1] + + +@unittest.skipUnless(LINUX, "not a Linux system") +class LinuxSpecificTestCase(unittest.TestCase): + + @unittest.skipIf( + POSIX and not hasattr(os, 'statvfs'), + reason="os.statvfs() function not available on this platform") + @skip_on_not_implemented() + def test_disks(self): + # test psutil.disk_usage() and psutil.disk_partitions() + # against "df -a" + def df(path): + out = sh('df -P -B 1 "%s"' % path).strip() + lines = out.split('\n') + lines.pop(0) + line = lines.pop(0) + dev, total, used, free = line.split()[:4] + if dev == 'none': + dev = '' + total, used, free = int(total), int(used), int(free) + return dev, total, used, free + + for part in psutil.disk_partitions(all=False): + usage = psutil.disk_usage(part.mountpoint) + dev, total, used, free = df(part.mountpoint) + self.assertEqual(part.device, dev) + self.assertEqual(usage.total, total) + # 10 MB tollerance + if abs(usage.free - free) > 10 * 1024 * 1024: + self.fail("psutil=%s, df=%s" % (usage.free, free)) + if abs(usage.used - used) > 10 * 1024 * 1024: + self.fail("psutil=%s, df=%s" % (usage.used, used)) + + def test_memory_maps(self): + sproc = get_test_subprocess() + time.sleep(1) + p = psutil.Process(sproc.pid) + maps = p.memory_maps(grouped=False) + pmap = sh('pmap -x %s' % p.pid).split('\n') + # get rid of header + del pmap[0] + del pmap[0] + while maps and pmap: + this = maps.pop(0) + other = pmap.pop(0) + addr, _, rss, dirty, mode, path = other.split(None, 5) + if not path.startswith('[') and not path.endswith(']'): + self.assertEqual(path, os.path.basename(this.path)) + self.assertEqual(int(rss) * 1024, this.rss) + # test only rwx chars, ignore 's' and 'p' + self.assertEqual(mode[:3], this.perms[:3]) + + def test_vmem_total(self): + lines = sh('free').split('\n')[1:] + total = int(lines[0].split()[1]) * 1024 + self.assertEqual(total, psutil.virtual_memory().total) + + @retry_before_failing() + def test_vmem_used(self): + lines = sh('free').split('\n')[1:] + used = int(lines[0].split()[2]) * 1024 + self.assertAlmostEqual(used, psutil.virtual_memory().used, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_free(self): + lines = sh('free').split('\n')[1:] + free = int(lines[0].split()[3]) * 1024 + self.assertAlmostEqual(free, psutil.virtual_memory().free, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_buffers(self): + lines = sh('free').split('\n')[1:] + buffers = int(lines[0].split()[5]) * 1024 + self.assertAlmostEqual(buffers, psutil.virtual_memory().buffers, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_cached(self): + lines = sh('free').split('\n')[1:] + cached = int(lines[0].split()[6]) * 1024 + self.assertAlmostEqual(cached, psutil.virtual_memory().cached, + delta=TOLERANCE) + + def test_swapmem_total(self): + lines = sh('free').split('\n')[1:] + total = int(lines[2].split()[1]) * 1024 + self.assertEqual(total, psutil.swap_memory().total) + + @retry_before_failing() + def test_swapmem_used(self): + lines = sh('free').split('\n')[1:] + used = int(lines[2].split()[2]) * 1024 + self.assertAlmostEqual(used, psutil.swap_memory().used, + delta=TOLERANCE) + + @retry_before_failing() + def test_swapmem_free(self): + lines = sh('free').split('\n')[1:] + free = int(lines[2].split()[3]) * 1024 + self.assertAlmostEqual(free, psutil.swap_memory().free, + delta=TOLERANCE) + + @unittest.skipIf(TRAVIS, "unknown failure on travis") + def test_cpu_times(self): + fields = psutil.cpu_times()._fields + kernel_ver = re.findall('\d+\.\d+\.\d+', os.uname()[2])[0] + kernel_ver_info = tuple(map(int, kernel_ver.split('.'))) + if kernel_ver_info >= (2, 6, 11): + self.assertIn('steal', fields) + else: + self.assertNotIn('steal', fields) + if kernel_ver_info >= (2, 6, 24): + self.assertIn('guest', fields) + else: + self.assertNotIn('guest', fields) + if kernel_ver_info >= (3, 2, 0): + self.assertIn('guest_nice', fields) + else: + self.assertNotIn('guest_nice', fields) + + def test_net_if_addrs_ips(self): + for name, addrs in psutil.net_if_addrs().items(): + for addr in addrs: + if addr.family == psutil.AF_LINK: + self.assertEqual(addr.address, get_mac_address(name)) + elif addr.family == socket.AF_INET: + self.assertEqual(addr.address, get_ipv4_address(name)) + # TODO: test for AF_INET6 family + + @unittest.skipUnless(which('ip'), "'ip' utility not available") + @unittest.skipIf(TRAVIS, "skipped on Travis") + def test_net_if_names(self): + out = sh("ip addr").strip() + nics = psutil.net_if_addrs() + found = 0 + for line in out.split('\n'): + line = line.strip() + if re.search("^\d+:", line): + found += 1 + name = line.split(':')[1].strip() + self.assertIn(name, nics.keys()) + self.assertEqual(len(nics), found, msg="%s\n---\n%s" % ( + pprint.pformat(nics), out)) + + @unittest.skipUnless(which("nproc"), "nproc utility not available") + def test_cpu_count_logical_w_nproc(self): + num = int(sh("nproc --all")) + self.assertEqual(psutil.cpu_count(logical=True), num) + + @unittest.skipUnless(which("lscpu"), "lscpu utility not available") + def test_cpu_count_logical_w_lscpu(self): + out = sh("lscpu -p") + num = len([x for x in out.split('\n') if not x.startswith('#')]) + self.assertEqual(psutil.cpu_count(logical=True), num) + + # --- mocked tests + + def test_virtual_memory_mocked_warnings(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + with warnings.catch_warnings(record=True) as ws: + warnings.simplefilter("always") + ret = psutil._pslinux.virtual_memory() + assert m.called + self.assertEqual(len(ws), 1) + w = ws[0] + self.assertTrue(w.filename.endswith('psutil/_pslinux.py')) + self.assertIn( + "'cached', 'active' and 'inactive' memory stats couldn't " + "be determined", str(w.message)) + self.assertEqual(ret.cached, 0) + self.assertEqual(ret.active, 0) + self.assertEqual(ret.inactive, 0) + + def test_swap_memory_mocked_warnings(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + with warnings.catch_warnings(record=True) as ws: + warnings.simplefilter("always") + ret = psutil._pslinux.swap_memory() + assert m.called + self.assertEqual(len(ws), 1) + w = ws[0] + self.assertTrue(w.filename.endswith('psutil/_pslinux.py')) + self.assertIn( + "'sin' and 'sout' swap memory stats couldn't " + "be determined", str(w.message)) + self.assertEqual(ret.sin, 0) + self.assertEqual(ret.sout, 0) + + def test_cpu_count_logical_mocked(self): + import psutil._pslinux + original = psutil._pslinux.cpu_count_logical() + # Here we want to mock os.sysconf("SC_NPROCESSORS_ONLN") in + # order to cause the parsing of /proc/cpuinfo and /proc/stat. + with mock.patch( + 'psutil._pslinux.os.sysconf', side_effect=ValueError) as m: + self.assertEqual(psutil._pslinux.cpu_count_logical(), original) + assert m.called + + # Let's have open() return emtpy data and make sure None is + # returned ('cause we mimick os.cpu_count()). + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertIsNone(psutil._pslinux.cpu_count_logical()) + self.assertEqual(m.call_count, 2) + # /proc/stat should be the last one + self.assertEqual(m.call_args[0][0], '/proc/stat') + + # Let's push this a bit further and make sure /proc/cpuinfo + # parsing works as expected. + with open('/proc/cpuinfo', 'rb') as f: + cpuinfo_data = f.read() + fake_file = io.BytesIO(cpuinfo_data) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + self.assertEqual(psutil._pslinux.cpu_count_logical(), original) + + def test_cpu_count_physical_mocked(self): + # Have open() return emtpy data and make sure None is returned + # ('cause we want to mimick os.cpu_count()) + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertIsNone(psutil._pslinux.cpu_count_physical()) + assert m.called + + def test_proc_open_files_file_gone(self): + # simulates a file which gets deleted during open_files() + # execution + p = psutil.Process() + files = p.open_files() + with tempfile.NamedTemporaryFile(): + # give the kernel some time to see the new file + call_until(p.open_files, "len(ret) != %i" % len(files)) + with mock.patch('psutil._pslinux.os.readlink', + side_effect=OSError(errno.ENOENT, "")) as m: + files = p.open_files() + assert not files + assert m.called + # also simulate the case where os.readlink() returns EINVAL + # in which case psutil is supposed to 'continue' + with mock.patch('psutil._pslinux.os.readlink', + side_effect=OSError(errno.EINVAL, "")) as m: + self.assertEqual(p.open_files(), []) + assert m.called + + def test_proc_terminal_mocked(self): + with mock.patch('psutil._pslinux._psposix._get_terminal_map', + return_value={}) as m: + self.assertIsNone(psutil._pslinux.Process(os.getpid()).terminal()) + assert m.called + + def test_proc_num_ctx_switches_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + NotImplementedError, + psutil._pslinux.Process(os.getpid()).num_ctx_switches) + assert m.called + + def test_proc_num_threads_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + NotImplementedError, + psutil._pslinux.Process(os.getpid()).num_threads) + assert m.called + + def test_proc_ppid_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + NotImplementedError, + psutil._pslinux.Process(os.getpid()).ppid) + assert m.called + + def test_proc_uids_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + NotImplementedError, + psutil._pslinux.Process(os.getpid()).uids) + assert m.called + + def test_proc_gids_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + NotImplementedError, + psutil._pslinux.Process(os.getpid()).gids) + assert m.called + + def test_proc_cmdline_mocked(self): + # see: https://github.com/giampaolo/psutil/issues/639 + p = psutil.Process() + fake_file = io.StringIO(u('foo\x00bar\x00')) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + p.cmdline() == ['foo', 'bar'] + assert m.called + fake_file = io.StringIO(u('foo\x00bar\x00\x00')) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m: + p.cmdline() == ['foo', 'bar', ''] + assert m.called + + def test_proc_io_counters_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + NotImplementedError, + psutil._pslinux.Process(os.getpid()).io_counters) + assert m.called + + def test_boot_time_mocked(self): + with mock.patch('psutil._pslinux.open', create=True) as m: + self.assertRaises( + RuntimeError, + psutil._pslinux.boot_time) + assert m.called + + def test_users_mocked(self): + # Make sure ':0' and ':0.0' (returned by C ext) are converted + # to 'localhost'. + with mock.patch('psutil._pslinux.cext.users', + return_value=[('giampaolo', 'pts/2', ':0', + 1436573184.0, True)]) as m: + self.assertEqual(psutil.users()[0].host, 'localhost') + assert m.called + with mock.patch('psutil._pslinux.cext.users', + return_value=[('giampaolo', 'pts/2', ':0.0', + 1436573184.0, True)]) as m: + self.assertEqual(psutil.users()[0].host, 'localhost') + assert m.called + # ...otherwise it should be returned as-is + with mock.patch('psutil._pslinux.cext.users', + return_value=[('giampaolo', 'pts/2', 'foo', + 1436573184.0, True)]) as m: + self.assertEqual(psutil.users()[0].host, 'foo') + assert m.called + + def test_disk_partitions_mocked(self): + # Test that ZFS partitions are returned. + with open("/proc/filesystems", "r") as f: + data = f.read() + if 'zfs' in data: + for part in psutil.disk_partitions(): + if part.fstype == 'zfs': + break + else: + self.fail("couldn't find any ZFS partition") + else: + # No ZFS partitions on this system. Let's fake one. + fake_file = io.StringIO(u("nodev\tzfs\n")) + with mock.patch('psutil._pslinux.open', + return_value=fake_file, create=True) as m1: + with mock.patch( + 'psutil._pslinux.cext.disk_partitions', + return_value=[('/dev/sdb3', '/', 'zfs', 'rw')]) as m2: + ret = psutil.disk_partitions() + assert m1.called + assert m2.called + assert ret + self.assertEqual(ret[0].fstype, 'zfs') + + # --- tests for specific kernel versions + + @unittest.skipUnless( + get_kernel_version() >= (2, 6, 36), + "prlimit() not available on this Linux kernel version") + def test_prlimit_availability(self): + # prlimit() should be available starting from kernel 2.6.36 + p = psutil.Process(os.getpid()) + p.rlimit(psutil.RLIMIT_NOFILE) + # if prlimit() is supported *at least* these constants should + # be available + self.assertTrue(hasattr(psutil, "RLIM_INFINITY")) + self.assertTrue(hasattr(psutil, "RLIMIT_AS")) + self.assertTrue(hasattr(psutil, "RLIMIT_CORE")) + self.assertTrue(hasattr(psutil, "RLIMIT_CPU")) + self.assertTrue(hasattr(psutil, "RLIMIT_DATA")) + self.assertTrue(hasattr(psutil, "RLIMIT_FSIZE")) + self.assertTrue(hasattr(psutil, "RLIMIT_LOCKS")) + self.assertTrue(hasattr(psutil, "RLIMIT_MEMLOCK")) + self.assertTrue(hasattr(psutil, "RLIMIT_NOFILE")) + self.assertTrue(hasattr(psutil, "RLIMIT_NPROC")) + self.assertTrue(hasattr(psutil, "RLIMIT_RSS")) + self.assertTrue(hasattr(psutil, "RLIMIT_STACK")) + + @unittest.skipUnless( + get_kernel_version() >= (3, 0), + "prlimit constants not available on this Linux kernel version") + def test_resource_consts_kernel_v(self): + # more recent constants + self.assertTrue(hasattr(psutil, "RLIMIT_MSGQUEUE")) + self.assertTrue(hasattr(psutil, "RLIMIT_NICE")) + self.assertTrue(hasattr(psutil, "RLIMIT_RTPRIO")) + self.assertTrue(hasattr(psutil, "RLIMIT_RTTIME")) + self.assertTrue(hasattr(psutil, "RLIMIT_SIGPENDING")) + + +def main(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(LinuxSpecificTestCase)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/_osx.py b/python/psutil/test/_osx.py new file mode 100644 index 0000000000..6e6e4380ef --- /dev/null +++ b/python/psutil/test/_osx.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python + +# 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. + +"""OSX specific tests. These are implicitly run by test_psutil.py.""" + +import os +import re +import subprocess +import sys +import time + +import psutil + +from psutil._compat import PY3 +from test_psutil import (TOLERANCE, OSX, sh, get_test_subprocess, + reap_children, retry_before_failing, unittest) + + +PAGESIZE = os.sysconf("SC_PAGE_SIZE") + + +def sysctl(cmdline): + """Expects a sysctl command with an argument and parse the result + returning only the value of interest. + """ + p = subprocess.Popen(cmdline, shell=1, stdout=subprocess.PIPE) + result = p.communicate()[0].strip().split()[1] + if PY3: + result = str(result, sys.stdout.encoding) + try: + return int(result) + except ValueError: + return result + + +def vm_stat(field): + """Wrapper around 'vm_stat' cmdline utility.""" + out = sh('vm_stat') + for line in out.split('\n'): + if field in line: + break + else: + raise ValueError("line not found") + return int(re.search('\d+', line).group(0)) * PAGESIZE + + +@unittest.skipUnless(OSX, "not an OSX system") +class OSXSpecificTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.pid = get_test_subprocess().pid + + @classmethod + def tearDownClass(cls): + reap_children() + + def test_process_create_time(self): + cmdline = "ps -o lstart -p %s" % self.pid + p = subprocess.Popen(cmdline, shell=1, stdout=subprocess.PIPE) + output = p.communicate()[0] + if PY3: + output = str(output, sys.stdout.encoding) + start_ps = output.replace('STARTED', '').strip() + start_psutil = psutil.Process(self.pid).create_time() + start_psutil = time.strftime("%a %b %e %H:%M:%S %Y", + time.localtime(start_psutil)) + self.assertEqual(start_ps, start_psutil) + + def test_disks(self): + # test psutil.disk_usage() and psutil.disk_partitions() + # against "df -a" + def df(path): + out = sh('df -k "%s"' % path).strip() + lines = out.split('\n') + lines.pop(0) + line = lines.pop(0) + dev, total, used, free = line.split()[:4] + if dev == 'none': + dev = '' + total = int(total) * 1024 + used = int(used) * 1024 + free = int(free) * 1024 + return dev, total, used, free + + for part in psutil.disk_partitions(all=False): + usage = psutil.disk_usage(part.mountpoint) + dev, total, used, free = df(part.mountpoint) + self.assertEqual(part.device, dev) + self.assertEqual(usage.total, total) + # 10 MB tollerance + if abs(usage.free - free) > 10 * 1024 * 1024: + self.fail("psutil=%s, df=%s" % usage.free, free) + if abs(usage.used - used) > 10 * 1024 * 1024: + self.fail("psutil=%s, df=%s" % usage.used, used) + + # --- virtual mem + + def test_vmem_total(self): + sysctl_hwphymem = sysctl('sysctl hw.memsize') + self.assertEqual(sysctl_hwphymem, psutil.virtual_memory().total) + + @retry_before_failing() + def test_vmem_free(self): + num = vm_stat("free") + self.assertAlmostEqual(psutil.virtual_memory().free, num, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_active(self): + num = vm_stat("active") + self.assertAlmostEqual(psutil.virtual_memory().active, num, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_inactive(self): + num = vm_stat("inactive") + self.assertAlmostEqual(psutil.virtual_memory().inactive, num, + delta=TOLERANCE) + + @retry_before_failing() + def test_vmem_wired(self): + num = vm_stat("wired") + self.assertAlmostEqual(psutil.virtual_memory().wired, num, + delta=TOLERANCE) + + # --- swap mem + + def test_swapmem_sin(self): + num = vm_stat("Pageins") + self.assertEqual(psutil.swap_memory().sin, num) + + def test_swapmem_sout(self): + num = vm_stat("Pageouts") + self.assertEqual(psutil.swap_memory().sout, num) + + def test_swapmem_total(self): + tot1 = psutil.swap_memory().total + tot2 = 0 + # OSX uses multiple cache files: + # http://en.wikipedia.org/wiki/Paging#OS_X + for name in os.listdir("/var/vm/"): + file = os.path.join("/var/vm", name) + if os.path.isfile(file): + tot2 += os.path.getsize(file) + self.assertEqual(tot1, tot2) + + +def main(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(OSXSpecificTestCase)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/_posix.py b/python/psutil/test/_posix.py new file mode 100644 index 0000000000..e6c56aac3e --- /dev/null +++ b/python/psutil/test/_posix.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python + +# 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. + +"""POSIX specific tests. These are implicitly run by test_psutil.py.""" + +import datetime +import os +import subprocess +import sys +import time + +import psutil + +from psutil._compat import PY3, callable +from test_psutil import LINUX, SUNOS, OSX, BSD, PYTHON, POSIX, TRAVIS +from test_psutil import (get_test_subprocess, skip_on_access_denied, + retry_before_failing, reap_children, sh, unittest, + get_kernel_version, wait_for_pid) + + +def ps(cmd): + """Expects a ps command with a -o argument and parse the result + returning only the value of interest. + """ + if not LINUX: + cmd = cmd.replace(" --no-headers ", " ") + if SUNOS: + cmd = cmd.replace("-o command", "-o comm") + cmd = cmd.replace("-o start", "-o stime") + p = subprocess.Popen(cmd, shell=1, stdout=subprocess.PIPE) + output = p.communicate()[0].strip() + if PY3: + output = str(output, sys.stdout.encoding) + if not LINUX: + output = output.split('\n')[1].strip() + try: + return int(output) + except ValueError: + return output + + +@unittest.skipUnless(POSIX, "not a POSIX system") +class PosixSpecificTestCase(unittest.TestCase): + """Compare psutil results against 'ps' command line utility.""" + + @classmethod + def setUpClass(cls): + cls.pid = get_test_subprocess([PYTHON, "-E", "-O"], + stdin=subprocess.PIPE).pid + wait_for_pid(cls.pid) + + @classmethod + def tearDownClass(cls): + reap_children() + + # for ps -o arguments see: http://unixhelp.ed.ac.uk/CGI/man-cgi?ps + + def test_process_parent_pid(self): + ppid_ps = ps("ps --no-headers -o ppid -p %s" % self.pid) + ppid_psutil = psutil.Process(self.pid).ppid() + self.assertEqual(ppid_ps, ppid_psutil) + + def test_process_uid(self): + uid_ps = ps("ps --no-headers -o uid -p %s" % self.pid) + uid_psutil = psutil.Process(self.pid).uids().real + self.assertEqual(uid_ps, uid_psutil) + + def test_process_gid(self): + gid_ps = ps("ps --no-headers -o rgid -p %s" % self.pid) + gid_psutil = psutil.Process(self.pid).gids().real + self.assertEqual(gid_ps, gid_psutil) + + def test_process_username(self): + username_ps = ps("ps --no-headers -o user -p %s" % self.pid) + username_psutil = psutil.Process(self.pid).username() + self.assertEqual(username_ps, username_psutil) + + @skip_on_access_denied() + @retry_before_failing() + def test_process_rss_memory(self): + # give python interpreter some time to properly initialize + # so that the results are the same + time.sleep(0.1) + rss_ps = ps("ps --no-headers -o rss -p %s" % self.pid) + rss_psutil = psutil.Process(self.pid).memory_info()[0] / 1024 + self.assertEqual(rss_ps, rss_psutil) + + @skip_on_access_denied() + @retry_before_failing() + def test_process_vsz_memory(self): + # give python interpreter some time to properly initialize + # so that the results are the same + time.sleep(0.1) + vsz_ps = ps("ps --no-headers -o vsz -p %s" % self.pid) + vsz_psutil = psutil.Process(self.pid).memory_info()[1] / 1024 + self.assertEqual(vsz_ps, vsz_psutil) + + def test_process_name(self): + # use command + arg since "comm" keyword not supported on all platforms + name_ps = ps("ps --no-headers -o command -p %s" % ( + self.pid)).split(' ')[0] + # remove path if there is any, from the command + name_ps = os.path.basename(name_ps).lower() + name_psutil = psutil.Process(self.pid).name().lower() + self.assertEqual(name_ps, name_psutil) + + @unittest.skipIf(OSX or BSD, + 'ps -o start not available') + def test_process_create_time(self): + time_ps = ps("ps --no-headers -o start -p %s" % self.pid).split(' ')[0] + time_psutil = psutil.Process(self.pid).create_time() + time_psutil_tstamp = datetime.datetime.fromtimestamp( + time_psutil).strftime("%H:%M:%S") + # sometimes ps shows the time rounded up instead of down, so we check + # for both possible values + round_time_psutil = round(time_psutil) + round_time_psutil_tstamp = datetime.datetime.fromtimestamp( + round_time_psutil).strftime("%H:%M:%S") + self.assertIn(time_ps, [time_psutil_tstamp, round_time_psutil_tstamp]) + + def test_process_exe(self): + ps_pathname = ps("ps --no-headers -o command -p %s" % + self.pid).split(' ')[0] + psutil_pathname = psutil.Process(self.pid).exe() + try: + self.assertEqual(ps_pathname, psutil_pathname) + except AssertionError: + # certain platforms such as BSD are more accurate returning: + # "/usr/local/bin/python2.7" + # ...instead of: + # "/usr/local/bin/python" + # We do not want to consider this difference in accuracy + # an error. + adjusted_ps_pathname = ps_pathname[:len(ps_pathname)] + self.assertEqual(ps_pathname, adjusted_ps_pathname) + + def test_process_cmdline(self): + ps_cmdline = ps("ps --no-headers -o command -p %s" % self.pid) + psutil_cmdline = " ".join(psutil.Process(self.pid).cmdline()) + if SUNOS: + # ps on Solaris only shows the first part of the cmdline + psutil_cmdline = psutil_cmdline.split(" ")[0] + self.assertEqual(ps_cmdline, psutil_cmdline) + + @retry_before_failing() + def test_pids(self): + # Note: this test might fail if the OS is starting/killing + # other processes in the meantime + if SUNOS: + cmd = ["ps", "ax"] + else: + cmd = ["ps", "ax", "-o", "pid"] + p = get_test_subprocess(cmd, stdout=subprocess.PIPE) + output = p.communicate()[0].strip() + if PY3: + output = str(output, sys.stdout.encoding) + pids_ps = [] + for line in output.split('\n')[1:]: + if line: + pid = int(line.split()[0].strip()) + pids_ps.append(pid) + # remove ps subprocess pid which is supposed to be dead in meantime + pids_ps.remove(p.pid) + pids_psutil = psutil.pids() + pids_ps.sort() + pids_psutil.sort() + + # on OSX ps doesn't show pid 0 + if OSX and 0 not in pids_ps: + pids_ps.insert(0, 0) + + if pids_ps != pids_psutil: + difference = [x for x in pids_psutil if x not in pids_ps] + \ + [x for x in pids_ps if x not in pids_psutil] + self.fail("difference: " + str(difference)) + + # for some reason ifconfig -a does not report all interfaces + # returned by psutil + @unittest.skipIf(SUNOS, "test not reliable on SUNOS") + @unittest.skipIf(TRAVIS, "test not reliable on Travis") + def test_nic_names(self): + p = subprocess.Popen("ifconfig -a", shell=1, stdout=subprocess.PIPE) + output = p.communicate()[0].strip() + if PY3: + output = str(output, sys.stdout.encoding) + for nic in psutil.net_io_counters(pernic=True).keys(): + for line in output.split(): + if line.startswith(nic): + break + else: + self.fail( + "couldn't find %s nic in 'ifconfig -a' output\n%s" % ( + nic, output)) + + @retry_before_failing() + def test_users(self): + out = sh("who") + lines = out.split('\n') + users = [x.split()[0] for x in lines] + self.assertEqual(len(users), len(psutil.users())) + terminals = [x.split()[1] for x in lines] + for u in psutil.users(): + self.assertTrue(u.name in users, u.name) + self.assertTrue(u.terminal in terminals, u.terminal) + + def test_fds_open(self): + # Note: this fails from time to time; I'm keen on thinking + # it doesn't mean something is broken + def call(p, attr): + args = () + attr = getattr(p, name, None) + if attr is not None and callable(attr): + if name == 'rlimit': + args = (psutil.RLIMIT_NOFILE,) + attr(*args) + else: + attr + + p = psutil.Process(os.getpid()) + failures = [] + ignored_names = ['terminate', 'kill', 'suspend', 'resume', 'nice', + 'send_signal', 'wait', 'children', 'as_dict'] + if LINUX and get_kernel_version() < (2, 6, 36): + ignored_names.append('rlimit') + if LINUX and get_kernel_version() < (2, 6, 23): + ignored_names.append('num_ctx_switches') + for name in dir(psutil.Process): + if (name.startswith('_') or name in ignored_names): + continue + else: + try: + num1 = p.num_fds() + for x in range(2): + call(p, name) + num2 = p.num_fds() + except psutil.AccessDenied: + pass + else: + if abs(num2 - num1) > 1: + fail = "failure while processing Process.%s method " \ + "(before=%s, after=%s)" % (name, num1, num2) + failures.append(fail) + if failures: + self.fail('\n' + '\n'.join(failures)) + + +def main(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(PosixSpecificTestCase)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/_sunos.py b/python/psutil/test/_sunos.py new file mode 100644 index 0000000000..3d54ccd8cf --- /dev/null +++ b/python/psutil/test/_sunos.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +# 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. + +"""Sun OS specific tests. These are implicitly run by test_psutil.py.""" + +import sys +import os + +from test_psutil import SUNOS, sh, unittest +import psutil + + +@unittest.skipUnless(SUNOS, "not a SunOS system") +class SunOSSpecificTestCase(unittest.TestCase): + + def test_swap_memory(self): + out = sh('env PATH=/usr/sbin:/sbin:%s swap -l -k' % os.environ['PATH']) + lines = out.strip().split('\n')[1:] + if not lines: + raise ValueError('no swap device(s) configured') + total = free = 0 + for line in lines: + line = line.split() + t, f = line[-2:] + t = t.replace('K', '') + f = f.replace('K', '') + total += int(int(t) * 1024) + free += int(int(f) * 1024) + used = total - free + + psutil_swap = psutil.swap_memory() + self.assertEqual(psutil_swap.total, total) + self.assertEqual(psutil_swap.used, used) + self.assertEqual(psutil_swap.free, free) + + +def main(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(SunOSSpecificTestCase)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/_windows.py b/python/psutil/test/_windows.py new file mode 100644 index 0000000000..b7477bfeb4 --- /dev/null +++ b/python/psutil/test/_windows.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python + +# 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. + +"""Windows specific tests. These are implicitly run by test_psutil.py.""" + +import errno +import os +import platform +import signal +import subprocess +import sys +import time +import traceback + +from test_psutil import APPVEYOR, WINDOWS +from test_psutil import get_test_subprocess, reap_children, unittest + +import mock +try: + import wmi +except ImportError: + wmi = None +try: + import win32api + import win32con +except ImportError: + win32api = win32con = None + +from psutil._compat import PY3, callable, long +import psutil + + +cext = psutil._psplatform.cext + + +def wrap_exceptions(fun): + def wrapper(self, *args, **kwargs): + try: + return fun(self, *args, **kwargs) + except OSError as err: + from psutil._pswindows import ACCESS_DENIED_SET + if err.errno in ACCESS_DENIED_SET: + raise psutil.AccessDenied(None, None) + if err.errno == errno.ESRCH: + raise psutil.NoSuchProcess(None, None) + raise + return wrapper + + +@unittest.skipUnless(WINDOWS, "not a Windows system") +class WindowsSpecificTestCase(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.pid = get_test_subprocess().pid + + @classmethod + def tearDownClass(cls): + reap_children() + + def test_issue_24(self): + p = psutil.Process(0) + self.assertRaises(psutil.AccessDenied, p.kill) + + def test_special_pid(self): + p = psutil.Process(4) + self.assertEqual(p.name(), 'System') + # use __str__ to access all common Process properties to check + # that nothing strange happens + str(p) + p.username() + self.assertTrue(p.create_time() >= 0.0) + try: + rss, vms = p.memory_info() + except psutil.AccessDenied: + # expected on Windows Vista and Windows 7 + if not platform.uname()[1] in ('vista', 'win-7', 'win7'): + raise + else: + self.assertTrue(rss > 0) + + def test_send_signal(self): + p = psutil.Process(self.pid) + self.assertRaises(ValueError, p.send_signal, signal.SIGINT) + + def test_nic_names(self): + p = subprocess.Popen(['ipconfig', '/all'], stdout=subprocess.PIPE) + out = p.communicate()[0] + if PY3: + out = str(out, sys.stdout.encoding) + nics = psutil.net_io_counters(pernic=True).keys() + for nic in nics: + if "pseudo-interface" in nic.replace(' ', '-').lower(): + continue + if nic not in out: + self.fail( + "%r nic wasn't found in 'ipconfig /all' output" % nic) + + def test_exe(self): + for p in psutil.process_iter(): + try: + self.assertEqual(os.path.basename(p.exe()), p.name()) + except psutil.Error: + pass + + # --- Process class tests + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_name(self): + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + self.assertEqual(p.name(), w.Caption) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_exe(self): + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + # Note: wmi reports the exe as a lower case string. + # Being Windows paths case-insensitive we ignore that. + self.assertEqual(p.exe().lower(), w.ExecutablePath.lower()) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_cmdline(self): + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + self.assertEqual(' '.join(p.cmdline()), + w.CommandLine.replace('"', '')) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_username(self): + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + domain, _, username = w.GetOwner() + username = "%s\\%s" % (domain, username) + self.assertEqual(p.username(), username) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_rss_memory(self): + time.sleep(0.1) + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + rss = p.memory_info().rss + self.assertEqual(rss, int(w.WorkingSetSize)) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_vms_memory(self): + time.sleep(0.1) + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + vms = p.memory_info().vms + # http://msdn.microsoft.com/en-us/library/aa394372(VS.85).aspx + # ...claims that PageFileUsage is represented in Kilo + # bytes but funnily enough on certain platforms bytes are + # returned instead. + wmi_usage = int(w.PageFileUsage) + if (vms != wmi_usage) and (vms != wmi_usage * 1024): + self.fail("wmi=%s, psutil=%s" % (wmi_usage, vms)) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_process_create_time(self): + w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + p = psutil.Process(self.pid) + wmic_create = str(w.CreationDate.split('.')[0]) + psutil_create = time.strftime("%Y%m%d%H%M%S", + time.localtime(p.create_time())) + self.assertEqual(wmic_create, psutil_create) + + # --- psutil namespace functions and constants tests + + @unittest.skipUnless('NUMBER_OF_PROCESSORS' in os.environ, + 'NUMBER_OF_PROCESSORS env var is not available') + def test_cpu_count(self): + num_cpus = int(os.environ['NUMBER_OF_PROCESSORS']) + self.assertEqual(num_cpus, psutil.cpu_count()) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_total_phymem(self): + w = wmi.WMI().Win32_ComputerSystem()[0] + self.assertEqual(int(w.TotalPhysicalMemory), + psutil.virtual_memory().total) + + # @unittest.skipIf(wmi is None, "wmi module is not installed") + # def test__UPTIME(self): + # # _UPTIME constant is not public but it is used internally + # # as value to return for pid 0 creation time. + # # WMI behaves the same. + # w = wmi.WMI().Win32_Process(ProcessId=self.pid)[0] + # p = psutil.Process(0) + # wmic_create = str(w.CreationDate.split('.')[0]) + # psutil_create = time.strftime("%Y%m%d%H%M%S", + # time.localtime(p.create_time())) + # + + # Note: this test is not very reliable + @unittest.skipIf(wmi is None, "wmi module is not installed") + @unittest.skipIf(APPVEYOR, "test not relieable on appveyor") + def test_pids(self): + # Note: this test might fail if the OS is starting/killing + # other processes in the meantime + w = wmi.WMI().Win32_Process() + wmi_pids = set([x.ProcessId for x in w]) + psutil_pids = set(psutil.pids()) + self.assertEqual(wmi_pids, psutil_pids) + + @unittest.skipIf(wmi is None, "wmi module is not installed") + def test_disks(self): + ps_parts = psutil.disk_partitions(all=True) + wmi_parts = wmi.WMI().Win32_LogicalDisk() + for ps_part in ps_parts: + for wmi_part in wmi_parts: + if ps_part.device.replace('\\', '') == wmi_part.DeviceID: + if not ps_part.mountpoint: + # this is usually a CD-ROM with no disk inserted + break + try: + usage = psutil.disk_usage(ps_part.mountpoint) + except OSError as err: + if err.errno == errno.ENOENT: + # usually this is the floppy + break + else: + raise + self.assertEqual(usage.total, int(wmi_part.Size)) + wmi_free = int(wmi_part.FreeSpace) + self.assertEqual(usage.free, wmi_free) + # 10 MB tollerance + if abs(usage.free - wmi_free) > 10 * 1024 * 1024: + self.fail("psutil=%s, wmi=%s" % ( + usage.free, wmi_free)) + break + else: + self.fail("can't find partition %s" % repr(ps_part)) + + @unittest.skipIf(win32api is None, "pywin32 module is not installed") + def test_num_handles(self): + p = psutil.Process(os.getpid()) + before = p.num_handles() + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, + win32con.FALSE, os.getpid()) + after = p.num_handles() + self.assertEqual(after, before + 1) + win32api.CloseHandle(handle) + self.assertEqual(p.num_handles(), before) + + @unittest.skipIf(win32api is None, "pywin32 module is not installed") + def test_num_handles_2(self): + # Note: this fails from time to time; I'm keen on thinking + # it doesn't mean something is broken + def call(p, attr): + attr = getattr(p, name, None) + if attr is not None and callable(attr): + attr() + else: + attr + + p = psutil.Process(self.pid) + failures = [] + for name in dir(psutil.Process): + if name.startswith('_') \ + or name in ('terminate', 'kill', 'suspend', 'resume', + 'nice', 'send_signal', 'wait', 'children', + 'as_dict'): + continue + else: + try: + call(p, name) + num1 = p.num_handles() + call(p, name) + num2 = p.num_handles() + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + else: + if num2 > num1: + fail = \ + "failure while processing Process.%s method " \ + "(before=%s, after=%s)" % (name, num1, num2) + failures.append(fail) + if failures: + self.fail('\n' + '\n'.join(failures)) + + def test_name_always_available(self): + # On Windows name() is never supposed to raise AccessDenied, + # see https://github.com/giampaolo/psutil/issues/627 + for p in psutil.process_iter(): + try: + p.name() + except psutil.NoSuchProcess(): + pass + + +@unittest.skipUnless(WINDOWS, "not a Windows system") +class TestDualProcessImplementation(unittest.TestCase): + """ + Certain APIs on Windows have 2 internal implementations, one + based on documented Windows APIs, another one based + NtQuerySystemInformation() which gets called as fallback in + case the first fails because of limited permission error. + Here we test that the two methods return the exact same value, + see: + https://github.com/giampaolo/psutil/issues/304 + """ + + fun_names = [ + # function name, tolerance + ('proc_cpu_times', 0.2), + ('proc_create_time', 0.5), + ('proc_num_handles', 1), # 1 because impl #1 opens a handle + ('proc_memory_info', 1024), # KB + ('proc_io_counters', 0), + ] + + def test_compare_values(self): + def assert_ge_0(obj): + if isinstance(obj, tuple): + for value in obj: + self.assertGreaterEqual(value, 0, msg=obj) + elif isinstance(obj, (int, long, float)): + self.assertGreaterEqual(obj, 0) + else: + assert 0 # case not handled which needs to be fixed + + def compare_with_tolerance(ret1, ret2, tolerance): + if ret1 == ret2: + return + else: + if isinstance(ret2, (int, long, float)): + diff = abs(ret1 - ret2) + self.assertLessEqual(diff, tolerance) + elif isinstance(ret2, tuple): + for a, b in zip(ret1, ret2): + diff = abs(a - b) + self.assertLessEqual(diff, tolerance) + + from psutil._pswindows import ntpinfo + failures = [] + for p in psutil.process_iter(): + try: + nt = ntpinfo(*cext.proc_info(p.pid)) + except psutil.NoSuchProcess: + continue + assert_ge_0(nt) + + for name, tolerance in self.fun_names: + if name == 'proc_memory_info' and p.pid == os.getpid(): + continue + if name == 'proc_create_time' and p.pid in (0, 4): + continue + meth = wrap_exceptions(getattr(cext, name)) + try: + ret = meth(p.pid) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + # compare values + try: + if name == 'proc_cpu_times': + compare_with_tolerance(ret[0], nt.user_time, tolerance) + compare_with_tolerance(ret[1], + nt.kernel_time, tolerance) + elif name == 'proc_create_time': + compare_with_tolerance(ret, nt.create_time, tolerance) + elif name == 'proc_num_handles': + compare_with_tolerance(ret, nt.num_handles, tolerance) + elif name == 'proc_io_counters': + compare_with_tolerance(ret[0], nt.io_rcount, tolerance) + compare_with_tolerance(ret[1], nt.io_wcount, tolerance) + compare_with_tolerance(ret[2], nt.io_rbytes, tolerance) + compare_with_tolerance(ret[3], nt.io_wbytes, tolerance) + elif name == 'proc_memory_info': + try: + rawtupl = cext.proc_memory_info_2(p.pid) + except psutil.NoSuchProcess: + continue + compare_with_tolerance(ret, rawtupl, tolerance) + except AssertionError: + trace = traceback.format_exc() + msg = '%s\npid=%s, method=%r, ret_1=%r, ret_2=%r' % ( + trace, p.pid, name, ret, nt) + failures.append(msg) + break + + if failures: + self.fail('\n\n'.join(failures)) + + # --- + # same tests as above but mimicks the AccessDenied failure of + # the first (fast) method failing with AD. + # TODO: currently does not take tolerance into account. + + def test_name(self): + name = psutil.Process().name() + with mock.patch("psutil._psplatform.cext.proc_exe", + side_effect=psutil.AccessDenied(os.getpid())) as fun: + psutil.Process().name() == name + assert fun.called + + def test_memory_info(self): + mem = psutil.Process().memory_info() + with mock.patch("psutil._psplatform.cext.proc_memory_info", + side_effect=OSError(errno.EPERM, "msg")) as fun: + psutil.Process().memory_info() == mem + assert fun.called + + def test_create_time(self): + ctime = psutil.Process().create_time() + with mock.patch("psutil._psplatform.cext.proc_create_time", + side_effect=OSError(errno.EPERM, "msg")) as fun: + psutil.Process().create_time() == ctime + assert fun.called + + def test_cpu_times(self): + cpu_times = psutil.Process().cpu_times() + with mock.patch("psutil._psplatform.cext.proc_cpu_times", + side_effect=OSError(errno.EPERM, "msg")) as fun: + psutil.Process().cpu_times() == cpu_times + assert fun.called + + def test_io_counters(self): + io_counters = psutil.Process().io_counters() + with mock.patch("psutil._psplatform.cext.proc_io_counters", + side_effect=OSError(errno.EPERM, "msg")) as fun: + psutil.Process().io_counters() == io_counters + assert fun.called + + def test_num_handles(self): + io_counters = psutil.Process().io_counters() + with mock.patch("psutil._psplatform.cext.proc_io_counters", + side_effect=OSError(errno.EPERM, "msg")) as fun: + psutil.Process().io_counters() == io_counters + assert fun.called + + # --- other tests + + def test_compare_name_exe(self): + for p in psutil.process_iter(): + try: + a = os.path.basename(p.exe()) + b = p.name() + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + else: + self.assertEqual(a, b) + + def test_zombies(self): + # test that NPS is raised by the 2nd implementation in case a + # process no longer exists + ZOMBIE_PID = max(psutil.pids()) + 5000 + for name, _ in self.fun_names: + meth = wrap_exceptions(getattr(cext, name)) + self.assertRaises(psutil.NoSuchProcess, meth, ZOMBIE_PID) + + +def main(): + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(WindowsSpecificTestCase)) + test_suite.addTest(unittest.makeSuite(TestDualProcessImplementation)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/test_memory_leaks.py b/python/psutil/test/test_memory_leaks.py new file mode 100644 index 0000000000..6f02dc0acf --- /dev/null +++ b/python/psutil/test/test_memory_leaks.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python + +# 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. + +""" +A test script which attempts to detect memory leaks by calling C +functions many times and compare process memory usage before and +after the calls. It might produce false positives. +""" + +import functools +import gc +import os +import socket +import sys +import threading +import time + +import psutil +import psutil._common + +from psutil._compat import xrange, callable +from test_psutil import (WINDOWS, POSIX, OSX, LINUX, SUNOS, BSD, TESTFN, + RLIMIT_SUPPORT, TRAVIS) +from test_psutil import (reap_children, supports_ipv6, safe_remove, + get_test_subprocess) + +if sys.version_info < (2, 7): + import unittest2 as unittest # https://pypi.python.org/pypi/unittest2 +else: + import unittest + + +LOOPS = 1000 +TOLERANCE = 4096 +SKIP_PYTHON_IMPL = True + + +def skip_if_linux(): + return unittest.skipIf(LINUX and SKIP_PYTHON_IMPL, + "not worth being tested on LINUX (pure python)") + + +class Base(unittest.TestCase): + proc = psutil.Process() + + def execute(self, function, *args, **kwargs): + def call_many_times(): + for x in xrange(LOOPS - 1): + self.call(function, *args, **kwargs) + del x + gc.collect() + return self.get_mem() + + self.call(function, *args, **kwargs) + self.assertEqual(gc.garbage, []) + self.assertEqual(threading.active_count(), 1) + + # RSS comparison + # step 1 + rss1 = call_many_times() + # step 2 + rss2 = call_many_times() + + difference = rss2 - rss1 + if difference > TOLERANCE: + # This doesn't necessarily mean we have a leak yet. + # At this point we assume that after having called the + # function so many times the memory usage is stabilized + # and if there are no leaks it should not increase any + # more. + # Let's keep calling fun for 3 more seconds and fail if + # we notice any difference. + stop_at = time.time() + 3 + while True: + self.call(function, *args, **kwargs) + if time.time() >= stop_at: + break + del stop_at + gc.collect() + rss3 = self.get_mem() + difference = rss3 - rss2 + if rss3 > rss2: + self.fail("rss2=%s, rss3=%s, difference=%s" + % (rss2, rss3, difference)) + + def execute_w_exc(self, exc, function, *args, **kwargs): + kwargs['_exc'] = exc + self.execute(function, *args, **kwargs) + + def get_mem(self): + return psutil.Process().memory_info()[0] + + def call(self, function, *args, **kwargs): + raise NotImplementedError("must be implemented in subclass") + + +class TestProcessObjectLeaks(Base): + """Test leaks of Process class methods and properties""" + + def setUp(self): + gc.collect() + + def tearDown(self): + reap_children() + + def call(self, function, *args, **kwargs): + if callable(function): + if '_exc' in kwargs: + exc = kwargs.pop('_exc') + self.assertRaises(exc, function, *args, **kwargs) + else: + try: + function(*args, **kwargs) + except psutil.Error: + pass + else: + meth = getattr(self.proc, function) + if '_exc' in kwargs: + exc = kwargs.pop('_exc') + self.assertRaises(exc, meth, *args, **kwargs) + else: + try: + meth(*args, **kwargs) + except psutil.Error: + pass + + @skip_if_linux() + def test_name(self): + self.execute('name') + + @skip_if_linux() + def test_cmdline(self): + self.execute('cmdline') + + @skip_if_linux() + def test_exe(self): + self.execute('exe') + + @skip_if_linux() + def test_ppid(self): + self.execute('ppid') + + @unittest.skipUnless(POSIX, "POSIX only") + @skip_if_linux() + def test_uids(self): + self.execute('uids') + + @unittest.skipUnless(POSIX, "POSIX only") + @skip_if_linux() + def test_gids(self): + self.execute('gids') + + @skip_if_linux() + def test_status(self): + self.execute('status') + + def test_nice_get(self): + self.execute('nice') + + def test_nice_set(self): + niceness = psutil.Process().nice() + self.execute('nice', niceness) + + @unittest.skipUnless(hasattr(psutil.Process, 'ionice'), + "Linux and Windows Vista only") + def test_ionice_get(self): + self.execute('ionice') + + @unittest.skipUnless(hasattr(psutil.Process, 'ionice'), + "Linux and Windows Vista only") + def test_ionice_set(self): + if WINDOWS: + value = psutil.Process().ionice() + self.execute('ionice', value) + else: + from psutil._pslinux import cext + self.execute('ionice', psutil.IOPRIO_CLASS_NONE) + fun = functools.partial(cext.proc_ioprio_set, os.getpid(), -1, 0) + self.execute_w_exc(OSError, fun) + + @unittest.skipIf(OSX or SUNOS, "feature not supported on this platform") + @skip_if_linux() + def test_io_counters(self): + self.execute('io_counters') + + @unittest.skipUnless(WINDOWS, "not worth being tested on posix") + def test_username(self): + self.execute('username') + + @skip_if_linux() + def test_create_time(self): + self.execute('create_time') + + @skip_if_linux() + def test_num_threads(self): + self.execute('num_threads') + + @unittest.skipUnless(WINDOWS, "Windows only") + def test_num_handles(self): + self.execute('num_handles') + + @unittest.skipUnless(POSIX, "POSIX only") + @skip_if_linux() + def test_num_fds(self): + self.execute('num_fds') + + @skip_if_linux() + def test_threads(self): + self.execute('threads') + + @skip_if_linux() + def test_cpu_times(self): + self.execute('cpu_times') + + @skip_if_linux() + def test_memory_info(self): + self.execute('memory_info') + + @skip_if_linux() + def test_memory_info_ex(self): + self.execute('memory_info_ex') + + @unittest.skipUnless(POSIX, "POSIX only") + @skip_if_linux() + def test_terminal(self): + self.execute('terminal') + + @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL, + "not worth being tested on POSIX (pure python)") + def test_resume(self): + self.execute('resume') + + @skip_if_linux() + def test_cwd(self): + self.execute('cwd') + + @unittest.skipUnless(WINDOWS or LINUX or BSD, + "Windows or Linux or BSD only") + def test_cpu_affinity_get(self): + self.execute('cpu_affinity') + + @unittest.skipUnless(WINDOWS or LINUX or BSD, + "Windows or Linux or BSD only") + def test_cpu_affinity_set(self): + affinity = psutil.Process().cpu_affinity() + self.execute('cpu_affinity', affinity) + if not TRAVIS: + self.execute_w_exc(ValueError, 'cpu_affinity', [-1]) + + @skip_if_linux() + def test_open_files(self): + safe_remove(TESTFN) # needed after UNIX socket test has run + with open(TESTFN, 'w'): + self.execute('open_files') + + # OSX implementation is unbelievably slow + @unittest.skipIf(OSX, "OSX implementation is too slow") + @skip_if_linux() + def test_memory_maps(self): + self.execute('memory_maps') + + @unittest.skipUnless(LINUX, "Linux only") + @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, + "only available on Linux >= 2.6.36") + def test_rlimit_get(self): + self.execute('rlimit', psutil.RLIMIT_NOFILE) + + @unittest.skipUnless(LINUX, "Linux only") + @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, + "only available on Linux >= 2.6.36") + def test_rlimit_set(self): + limit = psutil.Process().rlimit(psutil.RLIMIT_NOFILE) + self.execute('rlimit', psutil.RLIMIT_NOFILE, limit) + self.execute_w_exc(OSError, 'rlimit', -1) + + @skip_if_linux() + # Windows implementation is based on a single system-wide function + @unittest.skipIf(WINDOWS, "tested later") + def test_connections(self): + def create_socket(family, type): + sock = socket.socket(family, type) + sock.bind(('', 0)) + if type == socket.SOCK_STREAM: + sock.listen(1) + return sock + + socks = [] + socks.append(create_socket(socket.AF_INET, socket.SOCK_STREAM)) + socks.append(create_socket(socket.AF_INET, socket.SOCK_DGRAM)) + if supports_ipv6(): + socks.append(create_socket(socket.AF_INET6, socket.SOCK_STREAM)) + socks.append(create_socket(socket.AF_INET6, socket.SOCK_DGRAM)) + if hasattr(socket, 'AF_UNIX'): + safe_remove(TESTFN) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.bind(TESTFN) + s.listen(1) + socks.append(s) + kind = 'all' + # TODO: UNIX sockets are temporarily implemented by parsing + # 'pfiles' cmd output; we don't want that part of the code to + # be executed. + if SUNOS: + kind = 'inet' + try: + self.execute('connections', kind=kind) + finally: + for s in socks: + s.close() + + +p = get_test_subprocess() +DEAD_PROC = psutil.Process(p.pid) +DEAD_PROC.kill() +DEAD_PROC.wait() +del p + + +class TestProcessObjectLeaksZombie(TestProcessObjectLeaks): + """Same as above but looks for leaks occurring when dealing with + zombie processes raising NoSuchProcess exception. + """ + proc = DEAD_PROC + + def call(self, *args, **kwargs): + try: + TestProcessObjectLeaks.call(self, *args, **kwargs) + except psutil.NoSuchProcess: + pass + + if not POSIX: + def test_kill(self): + self.execute('kill') + + def test_terminate(self): + self.execute('terminate') + + def test_suspend(self): + self.execute('suspend') + + def test_resume(self): + self.execute('resume') + + def test_wait(self): + self.execute('wait') + + +class TestModuleFunctionsLeaks(Base): + """Test leaks of psutil module functions.""" + + def setUp(self): + gc.collect() + + def call(self, function, *args, **kwargs): + fun = getattr(psutil, function) + fun(*args, **kwargs) + + @skip_if_linux() + def test_cpu_count_logical(self): + psutil.cpu_count = psutil._psplatform.cpu_count_logical + self.execute('cpu_count') + + @skip_if_linux() + def test_cpu_count_physical(self): + psutil.cpu_count = psutil._psplatform.cpu_count_physical + self.execute('cpu_count') + + @skip_if_linux() + def test_boot_time(self): + self.execute('boot_time') + + @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL, + "not worth being tested on POSIX (pure python)") + def test_pid_exists(self): + self.execute('pid_exists', os.getpid()) + + def test_virtual_memory(self): + self.execute('virtual_memory') + + # TODO: remove this skip when this gets fixed + @unittest.skipIf(SUNOS, + "not worth being tested on SUNOS (uses a subprocess)") + def test_swap_memory(self): + self.execute('swap_memory') + + @skip_if_linux() + def test_cpu_times(self): + self.execute('cpu_times') + + @skip_if_linux() + def test_per_cpu_times(self): + self.execute('cpu_times', percpu=True) + + @unittest.skipIf(POSIX and SKIP_PYTHON_IMPL, + "not worth being tested on POSIX (pure python)") + def test_disk_usage(self): + self.execute('disk_usage', '.') + + def test_disk_partitions(self): + self.execute('disk_partitions') + + @skip_if_linux() + def test_net_io_counters(self): + self.execute('net_io_counters') + + @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'), + '/proc/diskstats not available on this Linux version') + @skip_if_linux() + def test_disk_io_counters(self): + self.execute('disk_io_counters') + + # XXX - on Windows this produces a false positive + @unittest.skipIf(WINDOWS, "XXX produces a false positive on Windows") + def test_users(self): + self.execute('users') + + @unittest.skipIf(LINUX, + "not worth being tested on Linux (pure python)") + def test_net_connections(self): + self.execute('net_connections') + + def test_net_if_addrs(self): + self.execute('net_if_addrs') + + @unittest.skipIf(TRAVIS, "EPERM on travis") + def test_net_if_stats(self): + self.execute('net_if_stats') + + +def main(): + test_suite = unittest.TestSuite() + tests = [TestProcessObjectLeaksZombie, + TestProcessObjectLeaks, + TestModuleFunctionsLeaks] + for test in tests: + test_suite.addTest(unittest.makeSuite(test)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/test/test_psutil.py b/python/psutil/test/test_psutil.py new file mode 100644 index 0000000000..3b2e3587ae --- /dev/null +++ b/python/psutil/test/test_psutil.py @@ -0,0 +1,3013 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# 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. + +""" +psutil test suite. Run it with: +$ make test + +If you're on Python < 2.7 unittest2 module must be installed first: +https://pypi.python.org/pypi/unittest2 +""" + +from __future__ import division + +import ast +import atexit +import collections +import contextlib +import datetime +import errno +import functools +import imp +import json +import os +import pickle +import pprint +import re +import select +import shutil +import signal +import socket +import stat +import subprocess +import sys +import tempfile +import textwrap +import threading +import time +import traceback +import types +import warnings +from socket import AF_INET, SOCK_STREAM, SOCK_DGRAM +try: + import ipaddress # python >= 3.3 +except ImportError: + ipaddress = None +try: + from unittest import mock # py3 +except ImportError: + import mock # requires "pip install mock" + +import psutil +from psutil._compat import PY3, callable, long, unicode + +if sys.version_info < (2, 7): + import unittest2 as unittest # https://pypi.python.org/pypi/unittest2 +else: + import unittest +if sys.version_info >= (3, 4): + import enum +else: + enum = None + + +# =================================================================== +# --- Constants +# =================================================================== + +# conf for retry_before_failing() decorator +NO_RETRIES = 10 +# bytes tolerance for OS memory related tests +TOLERANCE = 500 * 1024 # 500KB +# the timeout used in functions which have to wait +GLOBAL_TIMEOUT = 3 + +AF_INET6 = getattr(socket, "AF_INET6") +AF_UNIX = getattr(socket, "AF_UNIX", None) +PYTHON = os.path.realpath(sys.executable) +DEVNULL = open(os.devnull, 'r+') +TESTFN = os.path.join(os.getcwd(), "$testfile") +TESTFN_UNICODE = TESTFN + "ƒőő" +TESTFILE_PREFIX = 'psutil-test-suite-' +if not PY3: + try: + TESTFN_UNICODE = unicode(TESTFN_UNICODE, sys.getfilesystemencoding()) + except UnicodeDecodeError: + TESTFN_UNICODE = TESTFN + "???" + +EXAMPLES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), + '..', 'examples')) + +POSIX = os.name == 'posix' +WINDOWS = os.name == 'nt' +if WINDOWS: + WIN_VISTA = (6, 0, 0) +LINUX = sys.platform.startswith("linux") +OSX = sys.platform.startswith("darwin") +BSD = sys.platform.startswith("freebsd") +SUNOS = sys.platform.startswith("sunos") +VALID_PROC_STATUSES = [getattr(psutil, x) for x in dir(psutil) + if x.startswith('STATUS_')] +# whether we're running this test suite on Travis (https://travis-ci.org/) +TRAVIS = bool(os.environ.get('TRAVIS')) +# whether we're running this test suite on Appveyor for Windows +# (http://www.appveyor.com/) +APPVEYOR = bool(os.environ.get('APPVEYOR')) + +if TRAVIS or 'tox' in sys.argv[0]: + import ipaddress +if TRAVIS or APPVEYOR: + GLOBAL_TIMEOUT = GLOBAL_TIMEOUT * 4 + + +# =================================================================== +# --- Utility functions +# =================================================================== + +def cleanup(): + reap_children(search_all=True) + safe_remove(TESTFN) + try: + safe_rmdir(TESTFN_UNICODE) + except UnicodeEncodeError: + pass + for path in _testfiles: + safe_remove(path) + +atexit.register(cleanup) +atexit.register(lambda: DEVNULL.close()) + + +_subprocesses_started = set() + + +def get_test_subprocess(cmd=None, stdout=DEVNULL, stderr=DEVNULL, + stdin=DEVNULL, wait=False): + """Return a subprocess.Popen object to use in tests. + By default stdout and stderr are redirected to /dev/null and the + python interpreter is used as test process. + If 'wait' is True attemps to make sure the process is in a + reasonably initialized state. + """ + if cmd is None: + pyline = "" + if wait: + pyline += "open(r'%s', 'w'); " % TESTFN + pyline += "import time; time.sleep(60);" + cmd_ = [PYTHON, "-c", pyline] + else: + cmd_ = cmd + sproc = subprocess.Popen(cmd_, stdout=stdout, stderr=stderr, stdin=stdin) + if wait: + if cmd is None: + stop_at = time.time() + 3 + while stop_at > time.time(): + if os.path.exists(TESTFN): + break + time.sleep(0.001) + else: + warn("couldn't make sure test file was actually created") + else: + wait_for_pid(sproc.pid) + _subprocesses_started.add(psutil.Process(sproc.pid)) + return sproc + + +_testfiles = [] + + +def pyrun(src): + """Run python code 'src' in a separate interpreter. + Return interpreter subprocess. + """ + if PY3: + src = bytes(src, 'ascii') + with tempfile.NamedTemporaryFile( + prefix=TESTFILE_PREFIX, delete=False) as f: + _testfiles.append(f.name) + f.write(src) + f.flush() + subp = get_test_subprocess([PYTHON, f.name], stdout=None, + stderr=None) + wait_for_pid(subp.pid) + return subp + + +def warn(msg): + """Raise a warning msg.""" + warnings.warn(msg, UserWarning) + + +def sh(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE): + """run cmd in a subprocess and return its output. + raises RuntimeError on error. + """ + p = subprocess.Popen(cmdline, shell=True, stdout=stdout, stderr=stderr) + stdout, stderr = p.communicate() + if p.returncode != 0: + raise RuntimeError(stderr) + if stderr: + warn(stderr) + if PY3: + stdout = str(stdout, sys.stdout.encoding) + return stdout.strip() + + +def which(program): + """Same as UNIX which command. Return None on command not found.""" + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + + +if POSIX: + def get_kernel_version(): + """Return a tuple such as (2, 6, 36).""" + s = "" + uname = os.uname()[2] + for c in uname: + if c.isdigit() or c == '.': + s += c + else: + break + if not s: + raise ValueError("can't parse %r" % uname) + minor = 0 + micro = 0 + nums = s.split('.') + major = int(nums[0]) + if len(nums) >= 2: + minor = int(nums[1]) + if len(nums) >= 3: + micro = int(nums[2]) + return (major, minor, micro) + + +if LINUX: + RLIMIT_SUPPORT = get_kernel_version() >= (2, 6, 36) +else: + RLIMIT_SUPPORT = False + + +def wait_for_pid(pid, timeout=GLOBAL_TIMEOUT): + """Wait for pid to show up in the process list then return. + Used in the test suite to give time the sub process to initialize. + """ + raise_at = time.time() + timeout + while True: + if pid in psutil.pids(): + # give it one more iteration to allow full initialization + time.sleep(0.01) + return + time.sleep(0.0001) + if time.time() >= raise_at: + raise RuntimeError("Timed out") + + +def wait_for_file(fname, timeout=GLOBAL_TIMEOUT, delete_file=True): + """Wait for a file to be written on disk.""" + stop_at = time.time() + 3 + while time.time() < stop_at: + try: + with open(fname, "r") as f: + data = f.read() + if not data: + continue + if delete_file: + os.remove(fname) + return data + except IOError: + time.sleep(0.001) + raise RuntimeError("timed out (couldn't read file)") + + +def reap_children(search_all=False): + """Kill any subprocess started by this test suite and ensure that + no zombies stick around to hog resources and create problems when + looking for refleaks. + """ + global _subprocesses_started + procs = _subprocesses_started.copy() + if search_all: + this_process = psutil.Process() + for p in this_process.children(recursive=True): + procs.add(p) + for p in procs: + try: + p.terminate() + except psutil.NoSuchProcess: + pass + gone, alive = psutil.wait_procs(procs, timeout=GLOBAL_TIMEOUT) + for p in alive: + warn("couldn't terminate process %s" % p) + try: + p.kill() + except psutil.NoSuchProcess: + pass + _, alive = psutil.wait_procs(alive, timeout=GLOBAL_TIMEOUT) + if alive: + warn("couldn't not kill processes %s" % str(alive)) + _subprocesses_started = set(alive) + + +def check_ip_address(addr, family): + """Attempts to check IP address's validity.""" + if enum and PY3: + assert isinstance(family, enum.IntEnum), family + if family == AF_INET: + octs = [int(x) for x in addr.split('.')] + assert len(octs) == 4, addr + for num in octs: + assert 0 <= num <= 255, addr + if ipaddress: + if not PY3: + addr = unicode(addr) + ipaddress.IPv4Address(addr) + elif family == AF_INET6: + assert isinstance(addr, str), addr + if ipaddress: + if not PY3: + addr = unicode(addr) + ipaddress.IPv6Address(addr) + elif family == psutil.AF_LINK: + assert re.match('([a-fA-F0-9]{2}[:|\-]?){6}', addr) is not None, addr + else: + raise ValueError("unknown family %r", family) + + +def check_connection_ntuple(conn): + """Check validity of a connection namedtuple.""" + valid_conn_states = [getattr(psutil, x) for x in dir(psutil) if + x.startswith('CONN_')] + assert conn[0] == conn.fd + assert conn[1] == conn.family + assert conn[2] == conn.type + assert conn[3] == conn.laddr + assert conn[4] == conn.raddr + assert conn[5] == conn.status + assert conn.type in (SOCK_STREAM, SOCK_DGRAM), repr(conn.type) + assert conn.family in (AF_INET, AF_INET6, AF_UNIX), repr(conn.family) + assert conn.status in valid_conn_states, conn.status + + # check IP address and port sanity + for addr in (conn.laddr, conn.raddr): + if not addr: + continue + if conn.family in (AF_INET, AF_INET6): + assert isinstance(addr, tuple), addr + ip, port = addr + assert isinstance(port, int), port + assert 0 <= port <= 65535, port + check_ip_address(ip, conn.family) + elif conn.family == AF_UNIX: + assert isinstance(addr, (str, None)), addr + else: + raise ValueError("unknown family %r", conn.family) + + if conn.family in (AF_INET, AF_INET6): + # actually try to bind the local socket; ignore IPv6 + # sockets as their address might be represented as + # an IPv4-mapped-address (e.g. "::127.0.0.1") + # and that's rejected by bind() + if conn.family == AF_INET: + s = socket.socket(conn.family, conn.type) + with contextlib.closing(s): + try: + s.bind((conn.laddr[0], 0)) + except socket.error as err: + if err.errno != errno.EADDRNOTAVAIL: + raise + elif conn.family == AF_UNIX: + assert not conn.raddr, repr(conn.raddr) + assert conn.status == psutil.CONN_NONE, conn.status + + if getattr(conn, 'fd', -1) != -1: + assert conn.fd > 0, conn + if hasattr(socket, 'fromfd') and not WINDOWS: + try: + dupsock = socket.fromfd(conn.fd, conn.family, conn.type) + except (socket.error, OSError) as err: + if err.args[0] != errno.EBADF: + raise + else: + with contextlib.closing(dupsock): + assert dupsock.family == conn.family + assert dupsock.type == conn.type + + +def safe_remove(file): + "Convenience function for removing temporary test files" + try: + os.remove(file) + except OSError as err: + if err.errno != errno.ENOENT: + # file is being used by another process + if WINDOWS and isinstance(err, WindowsError) and err.errno == 13: + return + raise + + +def safe_rmdir(dir): + "Convenience function for removing temporary test directories" + try: + os.rmdir(dir) + except OSError as err: + if err.errno != errno.ENOENT: + raise + + +def call_until(fun, expr, timeout=GLOBAL_TIMEOUT): + """Keep calling function for timeout secs and exit if eval() + expression is True. + """ + stop_at = time.time() + timeout + while time.time() < stop_at: + ret = fun() + if eval(expr): + return ret + time.sleep(0.001) + raise RuntimeError('timed out (ret=%r)' % ret) + + +def retry_before_failing(ntimes=None): + """Decorator which runs a test function and retries N times before + actually failing. + """ + def decorator(fun): + @functools.wraps(fun) + def wrapper(*args, **kwargs): + for x in range(ntimes or NO_RETRIES): + try: + return fun(*args, **kwargs) + except AssertionError: + pass + raise + return wrapper + return decorator + + +def skip_on_access_denied(only_if=None): + """Decorator to Ignore AccessDenied exceptions.""" + def decorator(fun): + @functools.wraps(fun) + def wrapper(*args, **kwargs): + try: + return fun(*args, **kwargs) + except psutil.AccessDenied: + if only_if is not None: + if not only_if: + raise + msg = "%r was skipped because it raised AccessDenied" \ + % fun.__name__ + raise unittest.SkipTest(msg) + return wrapper + return decorator + + +def skip_on_not_implemented(only_if=None): + """Decorator to Ignore NotImplementedError exceptions.""" + def decorator(fun): + @functools.wraps(fun) + def wrapper(*args, **kwargs): + try: + return fun(*args, **kwargs) + except NotImplementedError: + if only_if is not None: + if not only_if: + raise + msg = "%r was skipped because it raised NotImplementedError" \ + % fun.__name__ + raise unittest.SkipTest(msg) + return wrapper + return decorator + + +def supports_ipv6(): + """Return True if IPv6 is supported on this platform.""" + if not socket.has_ipv6 or not hasattr(socket, "AF_INET6"): + return False + sock = None + try: + sock = socket.socket(AF_INET6, SOCK_STREAM) + sock.bind(("::1", 0)) + except (socket.error, socket.gaierror): + return False + else: + return True + finally: + if sock is not None: + sock.close() + + +if WINDOWS: + def get_winver(): + wv = sys.getwindowsversion() + if hasattr(wv, 'service_pack_major'): # python >= 2.7 + sp = wv.service_pack_major or 0 + else: + r = re.search("\s\d$", wv[4]) + if r: + sp = int(r.group(0)) + else: + sp = 0 + return (wv[0], wv[1], sp) + + +class ThreadTask(threading.Thread): + """A thread object used for running process thread tests.""" + + def __init__(self): + threading.Thread.__init__(self) + self._running = False + self._interval = None + self._flag = threading.Event() + + def __repr__(self): + name = self.__class__.__name__ + return '<%s running=%s at %#x>' % (name, self._running, id(self)) + + def start(self, interval=0.001): + """Start thread and keep it running until an explicit + stop() request. Polls for shutdown every 'timeout' seconds. + """ + if self._running: + raise ValueError("already started") + self._interval = interval + threading.Thread.start(self) + self._flag.wait() + + def run(self): + self._running = True + self._flag.set() + while self._running: + time.sleep(self._interval) + + def stop(self): + """Stop thread execution and and waits until it is stopped.""" + if not self._running: + raise ValueError("already stopped") + self._running = False + self.join() + + +# =================================================================== +# --- System-related API tests +# =================================================================== + +class TestSystemAPIs(unittest.TestCase): + """Tests for system-related APIs.""" + + def setUp(self): + safe_remove(TESTFN) + + def tearDown(self): + reap_children() + + def test_process_iter(self): + self.assertIn(os.getpid(), [x.pid for x in psutil.process_iter()]) + sproc = get_test_subprocess() + self.assertIn(sproc.pid, [x.pid for x in psutil.process_iter()]) + p = psutil.Process(sproc.pid) + p.kill() + p.wait() + self.assertNotIn(sproc.pid, [x.pid for x in psutil.process_iter()]) + + def test_wait_procs(self): + def callback(p): + l.append(p.pid) + + l = [] + sproc1 = get_test_subprocess() + sproc2 = get_test_subprocess() + sproc3 = get_test_subprocess() + procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)] + self.assertRaises(ValueError, psutil.wait_procs, procs, timeout=-1) + self.assertRaises(TypeError, psutil.wait_procs, procs, callback=1) + t = time.time() + gone, alive = psutil.wait_procs(procs, timeout=0.01, callback=callback) + + self.assertLess(time.time() - t, 0.5) + self.assertEqual(gone, []) + self.assertEqual(len(alive), 3) + self.assertEqual(l, []) + for p in alive: + self.assertFalse(hasattr(p, 'returncode')) + + @retry_before_failing(30) + def test(procs, callback): + gone, alive = psutil.wait_procs(procs, timeout=0.03, + callback=callback) + self.assertEqual(len(gone), 1) + self.assertEqual(len(alive), 2) + return gone, alive + + sproc3.terminate() + gone, alive = test(procs, callback) + self.assertIn(sproc3.pid, [x.pid for x in gone]) + if POSIX: + self.assertEqual(gone.pop().returncode, signal.SIGTERM) + else: + self.assertEqual(gone.pop().returncode, 1) + self.assertEqual(l, [sproc3.pid]) + for p in alive: + self.assertFalse(hasattr(p, 'returncode')) + + @retry_before_failing(30) + def test(procs, callback): + gone, alive = psutil.wait_procs(procs, timeout=0.03, + callback=callback) + self.assertEqual(len(gone), 3) + self.assertEqual(len(alive), 0) + return gone, alive + + sproc1.terminate() + sproc2.terminate() + gone, alive = test(procs, callback) + self.assertEqual(set(l), set([sproc1.pid, sproc2.pid, sproc3.pid])) + for p in gone: + self.assertTrue(hasattr(p, 'returncode')) + + def test_wait_procs_no_timeout(self): + sproc1 = get_test_subprocess() + sproc2 = get_test_subprocess() + sproc3 = get_test_subprocess() + procs = [psutil.Process(x.pid) for x in (sproc1, sproc2, sproc3)] + for p in procs: + p.terminate() + gone, alive = psutil.wait_procs(procs) + + def test_boot_time(self): + bt = psutil.boot_time() + self.assertIsInstance(bt, float) + self.assertGreater(bt, 0) + self.assertLess(bt, time.time()) + + @unittest.skipUnless(POSIX, 'posix only') + def test_PAGESIZE(self): + # pagesize is used internally to perform different calculations + # and it's determined by using SC_PAGE_SIZE; make sure + # getpagesize() returns the same value. + import resource + self.assertEqual(os.sysconf("SC_PAGE_SIZE"), resource.getpagesize()) + + def test_virtual_memory(self): + mem = psutil.virtual_memory() + assert mem.total > 0, mem + assert mem.available > 0, mem + assert 0 <= mem.percent <= 100, mem + assert mem.used > 0, mem + assert mem.free >= 0, mem + for name in mem._fields: + value = getattr(mem, name) + if name != 'percent': + self.assertIsInstance(value, (int, long)) + if name != 'total': + if not value >= 0: + self.fail("%r < 0 (%s)" % (name, value)) + if value > mem.total: + self.fail("%r > total (total=%s, %s=%s)" + % (name, mem.total, name, value)) + + def test_swap_memory(self): + mem = psutil.swap_memory() + assert mem.total >= 0, mem + assert mem.used >= 0, mem + if mem.total > 0: + # likely a system with no swap partition + assert mem.free > 0, mem + else: + assert mem.free == 0, mem + assert 0 <= mem.percent <= 100, mem + assert mem.sin >= 0, mem + assert mem.sout >= 0, mem + + def test_pid_exists(self): + sproc = get_test_subprocess(wait=True) + self.assertTrue(psutil.pid_exists(sproc.pid)) + p = psutil.Process(sproc.pid) + p.kill() + p.wait() + self.assertFalse(psutil.pid_exists(sproc.pid)) + self.assertFalse(psutil.pid_exists(-1)) + self.assertEqual(psutil.pid_exists(0), 0 in psutil.pids()) + # pid 0 + psutil.pid_exists(0) == 0 in psutil.pids() + + def test_pid_exists_2(self): + reap_children() + pids = psutil.pids() + for pid in pids: + try: + assert psutil.pid_exists(pid) + except AssertionError: + # in case the process disappeared in meantime fail only + # if it is no longer in psutil.pids() + time.sleep(.1) + if pid in psutil.pids(): + self.fail(pid) + pids = range(max(pids) + 5000, max(pids) + 6000) + for pid in pids: + self.assertFalse(psutil.pid_exists(pid), msg=pid) + + def test_pids(self): + plist = [x.pid for x in psutil.process_iter()] + pidlist = psutil.pids() + self.assertEqual(plist.sort(), pidlist.sort()) + # make sure every pid is unique + self.assertEqual(len(pidlist), len(set(pidlist))) + + def test_test(self): + # test for psutil.test() function + stdout = sys.stdout + sys.stdout = DEVNULL + try: + psutil.test() + finally: + sys.stdout = stdout + + def test_cpu_count(self): + logical = psutil.cpu_count() + self.assertEqual(logical, len(psutil.cpu_times(percpu=True))) + self.assertGreaterEqual(logical, 1) + # + if LINUX: + with open("/proc/cpuinfo") as fd: + cpuinfo_data = fd.read() + if "physical id" not in cpuinfo_data: + raise unittest.SkipTest("cpuinfo doesn't include physical id") + physical = psutil.cpu_count(logical=False) + self.assertGreaterEqual(physical, 1) + self.assertGreaterEqual(logical, physical) + + def test_sys_cpu_times(self): + total = 0 + times = psutil.cpu_times() + sum(times) + for cp_time in times: + self.assertIsInstance(cp_time, float) + self.assertGreaterEqual(cp_time, 0.0) + total += cp_time + self.assertEqual(total, sum(times)) + str(times) + if not WINDOWS: + # CPU times are always supposed to increase over time or + # remain the same but never go backwards, see: + # https://github.com/giampaolo/psutil/issues/392 + last = psutil.cpu_times() + for x in range(100): + new = psutil.cpu_times() + for field in new._fields: + new_t = getattr(new, field) + last_t = getattr(last, field) + self.assertGreaterEqual(new_t, last_t, + msg="%s %s" % (new_t, last_t)) + last = new + + def test_sys_cpu_times2(self): + t1 = sum(psutil.cpu_times()) + time.sleep(0.1) + t2 = sum(psutil.cpu_times()) + difference = t2 - t1 + if not difference >= 0.05: + self.fail("difference %s" % difference) + + def test_sys_per_cpu_times(self): + for times in psutil.cpu_times(percpu=True): + total = 0 + sum(times) + for cp_time in times: + self.assertIsInstance(cp_time, float) + self.assertGreaterEqual(cp_time, 0.0) + total += cp_time + self.assertEqual(total, sum(times)) + str(times) + self.assertEqual(len(psutil.cpu_times(percpu=True)[0]), + len(psutil.cpu_times(percpu=False))) + + # Note: in theory CPU times are always supposed to increase over + # time or remain the same but never go backwards. In practice + # sometimes this is not the case. + # This issue seemd to be afflict Windows: + # https://github.com/giampaolo/psutil/issues/392 + # ...but it turns out also Linux (rarely) behaves the same. + # last = psutil.cpu_times(percpu=True) + # for x in range(100): + # new = psutil.cpu_times(percpu=True) + # for index in range(len(new)): + # newcpu = new[index] + # lastcpu = last[index] + # for field in newcpu._fields: + # new_t = getattr(newcpu, field) + # last_t = getattr(lastcpu, field) + # self.assertGreaterEqual( + # new_t, last_t, msg="%s %s" % (lastcpu, newcpu)) + # last = new + + def test_sys_per_cpu_times_2(self): + tot1 = psutil.cpu_times(percpu=True) + stop_at = time.time() + 0.1 + while True: + if time.time() >= stop_at: + break + tot2 = psutil.cpu_times(percpu=True) + for t1, t2 in zip(tot1, tot2): + t1, t2 = sum(t1), sum(t2) + difference = t2 - t1 + if difference >= 0.05: + return + self.fail() + + def _test_cpu_percent(self, percent, last_ret, new_ret): + try: + self.assertIsInstance(percent, float) + self.assertGreaterEqual(percent, 0.0) + self.assertIsNot(percent, -0.0) + self.assertLessEqual(percent, 100.0 * psutil.cpu_count()) + except AssertionError as err: + raise AssertionError("\n%s\nlast=%s\nnew=%s" % ( + err, pprint.pformat(last_ret), pprint.pformat(new_ret))) + + def test_sys_cpu_percent(self): + last = psutil.cpu_percent(interval=0.001) + for x in range(100): + new = psutil.cpu_percent(interval=None) + self._test_cpu_percent(new, last, new) + last = new + + def test_sys_per_cpu_percent(self): + last = psutil.cpu_percent(interval=0.001, percpu=True) + self.assertEqual(len(last), psutil.cpu_count()) + for x in range(100): + new = psutil.cpu_percent(interval=None, percpu=True) + for percent in new: + self._test_cpu_percent(percent, last, new) + last = new + + def test_sys_cpu_times_percent(self): + last = psutil.cpu_times_percent(interval=0.001) + for x in range(100): + new = psutil.cpu_times_percent(interval=None) + for percent in new: + self._test_cpu_percent(percent, last, new) + self._test_cpu_percent(sum(new), last, new) + last = new + + def test_sys_per_cpu_times_percent(self): + last = psutil.cpu_times_percent(interval=0.001, percpu=True) + self.assertEqual(len(last), psutil.cpu_count()) + for x in range(100): + new = psutil.cpu_times_percent(interval=None, percpu=True) + for cpu in new: + for percent in cpu: + self._test_cpu_percent(percent, last, new) + self._test_cpu_percent(sum(cpu), last, new) + last = new + + def test_sys_per_cpu_times_percent_negative(self): + # see: https://github.com/giampaolo/psutil/issues/645 + psutil.cpu_times_percent(percpu=True) + zero_times = [x._make([0 for x in range(len(x._fields))]) + for x in psutil.cpu_times(percpu=True)] + with mock.patch('psutil.cpu_times', return_value=zero_times): + for cpu in psutil.cpu_times_percent(percpu=True): + for percent in cpu: + self._test_cpu_percent(percent, None, None) + + @unittest.skipIf(POSIX and not hasattr(os, 'statvfs'), + "os.statvfs() function not available on this platform") + def test_disk_usage(self): + usage = psutil.disk_usage(os.getcwd()) + assert usage.total > 0, usage + assert usage.used > 0, usage + assert usage.free > 0, usage + assert usage.total > usage.used, usage + assert usage.total > usage.free, usage + assert 0 <= usage.percent <= 100, usage.percent + if hasattr(shutil, 'disk_usage'): + # py >= 3.3, see: http://bugs.python.org/issue12442 + shutil_usage = shutil.disk_usage(os.getcwd()) + tolerance = 5 * 1024 * 1024 # 5MB + self.assertEqual(usage.total, shutil_usage.total) + self.assertAlmostEqual(usage.free, shutil_usage.free, + delta=tolerance) + self.assertAlmostEqual(usage.used, shutil_usage.used, + delta=tolerance) + + # if path does not exist OSError ENOENT is expected across + # all platforms + fname = tempfile.mktemp() + try: + psutil.disk_usage(fname) + except OSError as err: + if err.args[0] != errno.ENOENT: + raise + else: + self.fail("OSError not raised") + + @unittest.skipIf(POSIX and not hasattr(os, 'statvfs'), + "os.statvfs() function not available on this platform") + def test_disk_usage_unicode(self): + # see: https://github.com/giampaolo/psutil/issues/416 + # XXX this test is not really reliable as it always fails on + # Python 3.X (2.X is fine) + try: + safe_rmdir(TESTFN_UNICODE) + os.mkdir(TESTFN_UNICODE) + psutil.disk_usage(TESTFN_UNICODE) + safe_rmdir(TESTFN_UNICODE) + except UnicodeEncodeError: + pass + + @unittest.skipIf(POSIX and not hasattr(os, 'statvfs'), + "os.statvfs() function not available on this platform") + @unittest.skipIf(LINUX and TRAVIS, "unknown failure on travis") + def test_disk_partitions(self): + # all = False + ls = psutil.disk_partitions(all=False) + # on travis we get: + # self.assertEqual(p.cpu_affinity(), [n]) + # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, 7,... != [0] + self.assertTrue(ls, msg=ls) + for disk in ls: + if WINDOWS and 'cdrom' in disk.opts: + continue + if not POSIX: + assert os.path.exists(disk.device), disk + else: + # we cannot make any assumption about this, see: + # http://goo.gl/p9c43 + disk.device + if SUNOS: + # on solaris apparently mount points can also be files + assert os.path.exists(disk.mountpoint), disk + else: + assert os.path.isdir(disk.mountpoint), disk + assert disk.fstype, disk + self.assertIsInstance(disk.opts, str) + + # all = True + ls = psutil.disk_partitions(all=True) + self.assertTrue(ls, msg=ls) + for disk in psutil.disk_partitions(all=True): + if not WINDOWS: + try: + os.stat(disk.mountpoint) + except OSError as err: + # http://mail.python.org/pipermail/python-dev/ + # 2012-June/120787.html + if err.errno not in (errno.EPERM, errno.EACCES): + raise + else: + if SUNOS: + # on solaris apparently mount points can also be files + assert os.path.exists(disk.mountpoint), disk + else: + assert os.path.isdir(disk.mountpoint), disk + self.assertIsInstance(disk.fstype, str) + self.assertIsInstance(disk.opts, str) + + def find_mount_point(path): + path = os.path.abspath(path) + while not os.path.ismount(path): + path = os.path.dirname(path) + return path + + mount = find_mount_point(__file__) + mounts = [x.mountpoint for x in psutil.disk_partitions(all=True)] + self.assertIn(mount, mounts) + psutil.disk_usage(mount) + + @skip_on_access_denied() + def test_net_connections(self): + def check(cons, families, types_): + for conn in cons: + self.assertIn(conn.family, families, msg=conn) + if conn.family != getattr(socket, 'AF_UNIX', object()): + self.assertIn(conn.type, types_, msg=conn) + + from psutil._common import conn_tmap + for kind, groups in conn_tmap.items(): + if SUNOS and kind == 'unix': + continue + families, types_ = groups + cons = psutil.net_connections(kind) + self.assertEqual(len(cons), len(set(cons))) + check(cons, families, types_) + + def test_net_io_counters(self): + def check_ntuple(nt): + self.assertEqual(nt[0], nt.bytes_sent) + self.assertEqual(nt[1], nt.bytes_recv) + self.assertEqual(nt[2], nt.packets_sent) + self.assertEqual(nt[3], nt.packets_recv) + self.assertEqual(nt[4], nt.errin) + self.assertEqual(nt[5], nt.errout) + self.assertEqual(nt[6], nt.dropin) + self.assertEqual(nt[7], nt.dropout) + assert nt.bytes_sent >= 0, nt + assert nt.bytes_recv >= 0, nt + assert nt.packets_sent >= 0, nt + assert nt.packets_recv >= 0, nt + assert nt.errin >= 0, nt + assert nt.errout >= 0, nt + assert nt.dropin >= 0, nt + assert nt.dropout >= 0, nt + + ret = psutil.net_io_counters(pernic=False) + check_ntuple(ret) + ret = psutil.net_io_counters(pernic=True) + self.assertNotEqual(ret, []) + for key in ret: + self.assertTrue(key) + check_ntuple(ret[key]) + + def test_net_if_addrs(self): + nics = psutil.net_if_addrs() + assert nics, nics + + # Not reliable on all platforms (net_if_addrs() reports more + # interfaces). + # self.assertEqual(sorted(nics.keys()), + # sorted(psutil.net_io_counters(pernic=True).keys())) + + families = set([socket.AF_INET, AF_INET6, psutil.AF_LINK]) + for nic, addrs in nics.items(): + self.assertEqual(len(set(addrs)), len(addrs)) + for addr in addrs: + self.assertIsInstance(addr.family, int) + self.assertIsInstance(addr.address, str) + self.assertIsInstance(addr.netmask, (str, type(None))) + self.assertIsInstance(addr.broadcast, (str, type(None))) + self.assertIn(addr.family, families) + if sys.version_info >= (3, 4): + self.assertIsInstance(addr.family, enum.IntEnum) + if addr.family == socket.AF_INET: + s = socket.socket(addr.family) + with contextlib.closing(s): + s.bind((addr.address, 0)) + elif addr.family == socket.AF_INET6: + info = socket.getaddrinfo( + addr.address, 0, socket.AF_INET6, socket.SOCK_STREAM, + 0, socket.AI_PASSIVE)[0] + af, socktype, proto, canonname, sa = info + s = socket.socket(af, socktype, proto) + with contextlib.closing(s): + s.bind(sa) + for ip in (addr.address, addr.netmask, addr.broadcast): + if ip is not None: + # TODO: skip AF_INET6 for now because I get: + # AddressValueError: Only hex digits permitted in + # u'c6f3%lxcbr0' in u'fe80::c8e0:fff:fe54:c6f3%lxcbr0' + if addr.family != AF_INET6: + check_ip_address(ip, addr.family) + + if BSD or OSX or SUNOS: + if hasattr(socket, "AF_LINK"): + self.assertEqual(psutil.AF_LINK, socket.AF_LINK) + elif LINUX: + self.assertEqual(psutil.AF_LINK, socket.AF_PACKET) + elif WINDOWS: + self.assertEqual(psutil.AF_LINK, -1) + + @unittest.skipIf(TRAVIS, "EPERM on travis") + def test_net_if_stats(self): + nics = psutil.net_if_stats() + assert nics, nics + all_duplexes = (psutil.NIC_DUPLEX_FULL, + psutil.NIC_DUPLEX_HALF, + psutil.NIC_DUPLEX_UNKNOWN) + for nic, stats in nics.items(): + isup, duplex, speed, mtu = stats + self.assertIsInstance(isup, bool) + self.assertIn(duplex, all_duplexes) + self.assertIn(duplex, all_duplexes) + self.assertGreaterEqual(speed, 0) + self.assertGreaterEqual(mtu, 0) + + @unittest.skipIf(LINUX and not os.path.exists('/proc/diskstats'), + '/proc/diskstats not available on this linux version') + @unittest.skipIf(APPVEYOR, + "can't find any physical disk on Appveyor") + def test_disk_io_counters(self): + def check_ntuple(nt): + self.assertEqual(nt[0], nt.read_count) + self.assertEqual(nt[1], nt.write_count) + self.assertEqual(nt[2], nt.read_bytes) + self.assertEqual(nt[3], nt.write_bytes) + self.assertEqual(nt[4], nt.read_time) + self.assertEqual(nt[5], nt.write_time) + assert nt.read_count >= 0, nt + assert nt.write_count >= 0, nt + assert nt.read_bytes >= 0, nt + assert nt.write_bytes >= 0, nt + assert nt.read_time >= 0, nt + assert nt.write_time >= 0, nt + + ret = psutil.disk_io_counters(perdisk=False) + check_ntuple(ret) + ret = psutil.disk_io_counters(perdisk=True) + # make sure there are no duplicates + self.assertEqual(len(ret), len(set(ret))) + for key in ret: + assert key, key + check_ntuple(ret[key]) + if LINUX and key[-1].isdigit(): + # if 'sda1' is listed 'sda' shouldn't, see: + # https://github.com/giampaolo/psutil/issues/338 + while key[-1].isdigit(): + key = key[:-1] + self.assertNotIn(key, ret.keys()) + + def test_users(self): + users = psutil.users() + if not APPVEYOR: + self.assertNotEqual(users, []) + for user in users: + assert user.name, user + user.terminal + user.host + assert user.started > 0.0, user + datetime.datetime.fromtimestamp(user.started) + + +# =================================================================== +# --- psutil.Process class tests +# =================================================================== + +class TestProcess(unittest.TestCase): + """Tests for psutil.Process class.""" + + def setUp(self): + safe_remove(TESTFN) + + def tearDown(self): + reap_children() + + def test_pid(self): + self.assertEqual(psutil.Process().pid, os.getpid()) + sproc = get_test_subprocess() + self.assertEqual(psutil.Process(sproc.pid).pid, sproc.pid) + + def test_kill(self): + sproc = get_test_subprocess(wait=True) + test_pid = sproc.pid + p = psutil.Process(test_pid) + p.kill() + sig = p.wait() + self.assertFalse(psutil.pid_exists(test_pid)) + if POSIX: + self.assertEqual(sig, signal.SIGKILL) + + def test_terminate(self): + sproc = get_test_subprocess(wait=True) + test_pid = sproc.pid + p = psutil.Process(test_pid) + p.terminate() + sig = p.wait() + self.assertFalse(psutil.pid_exists(test_pid)) + if POSIX: + self.assertEqual(sig, signal.SIGTERM) + + def test_send_signal(self): + sig = signal.SIGKILL if POSIX else signal.SIGTERM + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.send_signal(sig) + exit_sig = p.wait() + self.assertFalse(psutil.pid_exists(p.pid)) + if POSIX: + self.assertEqual(exit_sig, sig) + # + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.send_signal(sig) + with mock.patch('psutil.os.kill', + side_effect=OSError(errno.ESRCH, "")) as fun: + with self.assertRaises(psutil.NoSuchProcess): + p.send_signal(sig) + assert fun.called + # + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.send_signal(sig) + with mock.patch('psutil.os.kill', + side_effect=OSError(errno.EPERM, "")) as fun: + with self.assertRaises(psutil.AccessDenied): + p.send_signal(sig) + assert fun.called + + def test_wait(self): + # check exit code signal + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.kill() + code = p.wait() + if POSIX: + self.assertEqual(code, signal.SIGKILL) + else: + self.assertEqual(code, 0) + self.assertFalse(p.is_running()) + + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.terminate() + code = p.wait() + if POSIX: + self.assertEqual(code, signal.SIGTERM) + else: + self.assertEqual(code, 0) + self.assertFalse(p.is_running()) + + # check sys.exit() code + code = "import time, sys; time.sleep(0.01); sys.exit(5);" + sproc = get_test_subprocess([PYTHON, "-c", code]) + p = psutil.Process(sproc.pid) + self.assertEqual(p.wait(), 5) + self.assertFalse(p.is_running()) + + # Test wait() issued twice. + # It is not supposed to raise NSP when the process is gone. + # On UNIX this should return None, on Windows it should keep + # returning the exit code. + sproc = get_test_subprocess([PYTHON, "-c", code]) + p = psutil.Process(sproc.pid) + self.assertEqual(p.wait(), 5) + self.assertIn(p.wait(), (5, None)) + + # test timeout + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.name() + self.assertRaises(psutil.TimeoutExpired, p.wait, 0.01) + + # timeout < 0 not allowed + self.assertRaises(ValueError, p.wait, -1) + + # XXX why is this skipped on Windows? + @unittest.skipUnless(POSIX, 'skipped on Windows') + def test_wait_non_children(self): + # test wait() against processes which are not our children + code = "import sys;" + code += "from subprocess import Popen, PIPE;" + code += "cmd = ['%s', '-c', 'import time; time.sleep(60)'];" % PYTHON + code += "sp = Popen(cmd, stdout=PIPE);" + code += "sys.stdout.write(str(sp.pid));" + sproc = get_test_subprocess([PYTHON, "-c", code], + stdout=subprocess.PIPE) + grandson_pid = int(sproc.stdout.read()) + grandson_proc = psutil.Process(grandson_pid) + try: + self.assertRaises(psutil.TimeoutExpired, grandson_proc.wait, 0.01) + grandson_proc.kill() + ret = grandson_proc.wait() + self.assertEqual(ret, None) + finally: + if grandson_proc.is_running(): + grandson_proc.kill() + grandson_proc.wait() + + def test_wait_timeout_0(self): + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + self.assertRaises(psutil.TimeoutExpired, p.wait, 0) + p.kill() + stop_at = time.time() + 2 + while True: + try: + code = p.wait(0) + except psutil.TimeoutExpired: + if time.time() >= stop_at: + raise + else: + break + if POSIX: + self.assertEqual(code, signal.SIGKILL) + else: + self.assertEqual(code, 0) + self.assertFalse(p.is_running()) + + def test_cpu_percent(self): + p = psutil.Process() + p.cpu_percent(interval=0.001) + p.cpu_percent(interval=0.001) + for x in range(100): + percent = p.cpu_percent(interval=None) + self.assertIsInstance(percent, float) + self.assertGreaterEqual(percent, 0.0) + if not POSIX: + self.assertLessEqual(percent, 100.0) + else: + self.assertGreaterEqual(percent, 0.0) + + def test_cpu_times(self): + times = psutil.Process().cpu_times() + assert (times.user > 0.0) or (times.system > 0.0), times + # make sure returned values can be pretty printed with strftime + time.strftime("%H:%M:%S", time.localtime(times.user)) + time.strftime("%H:%M:%S", time.localtime(times.system)) + + # Test Process.cpu_times() against os.times() + # os.times() is broken on Python 2.6 + # http://bugs.python.org/issue1040026 + # XXX fails on OSX: not sure if it's for os.times(). We should + # try this with Python 2.7 and re-enable the test. + + @unittest.skipUnless(sys.version_info > (2, 6, 1) and not OSX, + 'os.times() is not reliable on this Python version') + def test_cpu_times2(self): + user_time, kernel_time = psutil.Process().cpu_times() + utime, ktime = os.times()[:2] + + # Use os.times()[:2] as base values to compare our results + # using a tolerance of +/- 0.1 seconds. + # It will fail if the difference between the values is > 0.1s. + if (max([user_time, utime]) - min([user_time, utime])) > 0.1: + self.fail("expected: %s, found: %s" % (utime, user_time)) + + if (max([kernel_time, ktime]) - min([kernel_time, ktime])) > 0.1: + self.fail("expected: %s, found: %s" % (ktime, kernel_time)) + + def test_create_time(self): + sproc = get_test_subprocess(wait=True) + now = time.time() + p = psutil.Process(sproc.pid) + create_time = p.create_time() + + # Use time.time() as base value to compare our result using a + # tolerance of +/- 1 second. + # It will fail if the difference between the values is > 2s. + difference = abs(create_time - now) + if difference > 2: + self.fail("expected: %s, found: %s, difference: %s" + % (now, create_time, difference)) + + # make sure returned value can be pretty printed with strftime + time.strftime("%Y %m %d %H:%M:%S", time.localtime(p.create_time())) + + @unittest.skipIf(WINDOWS, 'Windows only') + def test_terminal(self): + terminal = psutil.Process().terminal() + if sys.stdin.isatty(): + self.assertEqual(terminal, sh('tty')) + else: + assert terminal, repr(terminal) + + @unittest.skipUnless(LINUX or BSD or WINDOWS, + 'not available on this platform') + @skip_on_not_implemented(only_if=LINUX) + def test_io_counters(self): + p = psutil.Process() + # test reads + io1 = p.io_counters() + with open(PYTHON, 'rb') as f: + f.read() + io2 = p.io_counters() + if not BSD: + assert io2.read_count > io1.read_count, (io1, io2) + self.assertEqual(io2.write_count, io1.write_count) + assert io2.read_bytes >= io1.read_bytes, (io1, io2) + assert io2.write_bytes >= io1.write_bytes, (io1, io2) + # test writes + io1 = p.io_counters() + with tempfile.TemporaryFile(prefix=TESTFILE_PREFIX) as f: + if PY3: + f.write(bytes("x" * 1000000, 'ascii')) + else: + f.write("x" * 1000000) + io2 = p.io_counters() + assert io2.write_count >= io1.write_count, (io1, io2) + assert io2.write_bytes >= io1.write_bytes, (io1, io2) + assert io2.read_count >= io1.read_count, (io1, io2) + assert io2.read_bytes >= io1.read_bytes, (io1, io2) + + @unittest.skipUnless(LINUX or (WINDOWS and get_winver() >= WIN_VISTA), + 'Linux and Windows Vista only') + @unittest.skipIf(LINUX and TRAVIS, "unknown failure on travis") + def test_ionice(self): + if LINUX: + from psutil import (IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE) + self.assertEqual(IOPRIO_CLASS_NONE, 0) + self.assertEqual(IOPRIO_CLASS_RT, 1) + self.assertEqual(IOPRIO_CLASS_BE, 2) + self.assertEqual(IOPRIO_CLASS_IDLE, 3) + p = psutil.Process() + try: + p.ionice(2) + ioclass, value = p.ionice() + if enum is not None: + self.assertIsInstance(ioclass, enum.IntEnum) + self.assertEqual(ioclass, 2) + self.assertEqual(value, 4) + # + p.ionice(3) + ioclass, value = p.ionice() + self.assertEqual(ioclass, 3) + self.assertEqual(value, 0) + # + p.ionice(2, 0) + ioclass, value = p.ionice() + self.assertEqual(ioclass, 2) + self.assertEqual(value, 0) + p.ionice(2, 7) + ioclass, value = p.ionice() + self.assertEqual(ioclass, 2) + self.assertEqual(value, 7) + # + self.assertRaises(ValueError, p.ionice, 2, 10) + self.assertRaises(ValueError, p.ionice, 2, -1) + self.assertRaises(ValueError, p.ionice, 4) + self.assertRaises(TypeError, p.ionice, 2, "foo") + self.assertRaisesRegexp( + ValueError, "can't specify value with IOPRIO_CLASS_NONE", + p.ionice, psutil.IOPRIO_CLASS_NONE, 1) + self.assertRaisesRegexp( + ValueError, "can't specify value with IOPRIO_CLASS_IDLE", + p.ionice, psutil.IOPRIO_CLASS_IDLE, 1) + self.assertRaisesRegexp( + ValueError, "'ioclass' argument must be specified", + p.ionice, value=1) + finally: + p.ionice(IOPRIO_CLASS_NONE) + else: + 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) + # + self.assertRaises(ValueError, p.ionice, 3) + self.assertRaises(TypeError, p.ionice, 2, 1) + + @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, + "only available on Linux >= 2.6.36") + def test_rlimit_get(self): + import resource + p = psutil.Process(os.getpid()) + names = [x for x in dir(psutil) if x.startswith('RLIMIT')] + assert names, names + for name in names: + value = getattr(psutil, name) + self.assertGreaterEqual(value, 0) + if name in dir(resource): + self.assertEqual(value, getattr(resource, name)) + self.assertEqual(p.rlimit(value), resource.getrlimit(value)) + else: + ret = p.rlimit(value) + self.assertEqual(len(ret), 2) + self.assertGreaterEqual(ret[0], -1) + self.assertGreaterEqual(ret[1], -1) + + @unittest.skipUnless(LINUX and RLIMIT_SUPPORT, + "only available on Linux >= 2.6.36") + def test_rlimit_set(self): + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.rlimit(psutil.RLIMIT_NOFILE, (5, 5)) + self.assertEqual(p.rlimit(psutil.RLIMIT_NOFILE), (5, 5)) + # If pid is 0 prlimit() applies to the calling process and + # we don't want that. + with self.assertRaises(ValueError): + psutil._psplatform.Process(0).rlimit(0) + with self.assertRaises(ValueError): + p.rlimit(psutil.RLIMIT_NOFILE, (5, 5, 5)) + + def test_num_threads(self): + # on certain platforms such as Linux we might test for exact + # thread number, since we always have with 1 thread per process, + # but this does not apply across all platforms (OSX, Windows) + p = psutil.Process() + step1 = p.num_threads() + + thread = ThreadTask() + thread.start() + try: + step2 = p.num_threads() + self.assertEqual(step2, step1 + 1) + thread.stop() + finally: + if thread._running: + thread.stop() + + @unittest.skipUnless(WINDOWS, 'Windows only') + def test_num_handles(self): + # a better test is done later into test/_windows.py + p = psutil.Process() + self.assertGreater(p.num_handles(), 0) + + def test_threads(self): + p = psutil.Process() + step1 = p.threads() + + thread = ThreadTask() + thread.start() + + try: + step2 = p.threads() + self.assertEqual(len(step2), len(step1) + 1) + # on Linux, first thread id is supposed to be this process + if LINUX: + self.assertEqual(step2[0].id, os.getpid()) + athread = step2[0] + # test named tuple + self.assertEqual(athread.id, athread[0]) + self.assertEqual(athread.user_time, athread[1]) + self.assertEqual(athread.system_time, athread[2]) + # test num threads + thread.stop() + finally: + if thread._running: + thread.stop() + + def test_memory_info(self): + p = psutil.Process() + + # step 1 - get a base value to compare our results + rss1, vms1 = p.memory_info() + percent1 = p.memory_percent() + self.assertGreater(rss1, 0) + self.assertGreater(vms1, 0) + + # step 2 - allocate some memory + memarr = [None] * 1500000 + + rss2, vms2 = p.memory_info() + percent2 = p.memory_percent() + # make sure that the memory usage bumped up + self.assertGreater(rss2, rss1) + self.assertGreaterEqual(vms2, vms1) # vms might be equal + self.assertGreater(percent2, percent1) + del memarr + + # def test_memory_info_ex(self): + # # tested later in fetch all test suite + + def test_memory_maps(self): + p = psutil.Process() + maps = p.memory_maps() + paths = [x for x in maps] + self.assertEqual(len(paths), len(set(paths))) + ext_maps = p.memory_maps(grouped=False) + + for nt in maps: + if not nt.path.startswith('['): + assert os.path.isabs(nt.path), nt.path + if POSIX: + assert os.path.exists(nt.path), nt.path + else: + # XXX - On Windows we have this strange behavior with + # 64 bit dlls: they are visible via explorer but cannot + # be accessed via os.stat() (wtf?). + if '64' not in os.path.basename(nt.path): + assert os.path.exists(nt.path), nt.path + for nt in ext_maps: + for fname in nt._fields: + value = getattr(nt, fname) + if fname == 'path': + continue + elif fname in ('addr', 'perms'): + assert value, value + else: + self.assertIsInstance(value, (int, long)) + assert value >= 0, value + + def test_memory_percent(self): + p = psutil.Process() + self.assertGreater(p.memory_percent(), 0.0) + + def test_is_running(self): + sproc = get_test_subprocess(wait=True) + p = psutil.Process(sproc.pid) + assert p.is_running() + assert p.is_running() + p.kill() + p.wait() + assert not p.is_running() + assert not p.is_running() + + def test_exe(self): + sproc = get_test_subprocess(wait=True) + exe = psutil.Process(sproc.pid).exe() + try: + self.assertEqual(exe, PYTHON) + except AssertionError: + if WINDOWS and len(exe) == len(PYTHON): + # on Windows we don't care about case sensitivity + self.assertEqual(exe.lower(), PYTHON.lower()) + else: + # certain platforms such as BSD are more accurate returning: + # "/usr/local/bin/python2.7" + # ...instead of: + # "/usr/local/bin/python" + # We do not want to consider this difference in accuracy + # an error. + ver = "%s.%s" % (sys.version_info[0], sys.version_info[1]) + self.assertEqual(exe.replace(ver, ''), PYTHON.replace(ver, '')) + + def test_cmdline(self): + cmdline = [PYTHON, "-c", "import time; time.sleep(60)"] + sproc = get_test_subprocess(cmdline, wait=True) + self.assertEqual(' '.join(psutil.Process(sproc.pid).cmdline()), + ' '.join(cmdline)) + + def test_name(self): + sproc = get_test_subprocess(PYTHON, wait=True) + name = psutil.Process(sproc.pid).name().lower() + pyexe = os.path.basename(os.path.realpath(sys.executable)).lower() + assert pyexe.startswith(name), (pyexe, name) + + @unittest.skipUnless(POSIX, "posix only") + # TODO: add support for other compilers + @unittest.skipUnless(which("gcc"), "gcc not available") + def test_prog_w_funky_name(self): + # Test that name(), exe() and cmdline() correctly handle programs + # with funky chars such as spaces and ")", see: + # https://github.com/giampaolo/psutil/issues/628 + funky_name = "/tmp/foo bar )" + _, c_file = tempfile.mkstemp(prefix='psutil-', suffix='.c', dir="/tmp") + self.addCleanup(lambda: safe_remove(c_file)) + self.addCleanup(lambda: safe_remove(funky_name)) + with open(c_file, "w") as f: + f.write("void main() { pause(); }") + subprocess.check_call(["gcc", c_file, "-o", funky_name]) + sproc = get_test_subprocess( + [funky_name, "arg1", "arg2", "", "arg3", ""]) + p = psutil.Process(sproc.pid) + # ...in order to try to prevent occasional failures on travis + wait_for_pid(p.pid) + self.assertEqual(p.name(), "foo bar )") + self.assertEqual(p.exe(), "/tmp/foo bar )") + self.assertEqual( + p.cmdline(), ["/tmp/foo bar )", "arg1", "arg2", "", "arg3", ""]) + + @unittest.skipUnless(POSIX, 'posix only') + def test_uids(self): + p = psutil.Process() + real, effective, saved = p.uids() + # os.getuid() refers to "real" uid + self.assertEqual(real, os.getuid()) + # os.geteuid() refers to "effective" uid + self.assertEqual(effective, os.geteuid()) + # no such thing as os.getsuid() ("saved" uid), but starting + # from python 2.7 we have os.getresuid()[2] + if hasattr(os, "getresuid"): + self.assertEqual(saved, os.getresuid()[2]) + + @unittest.skipUnless(POSIX, 'posix only') + def test_gids(self): + p = psutil.Process() + real, effective, saved = p.gids() + # os.getuid() refers to "real" uid + self.assertEqual(real, os.getgid()) + # os.geteuid() refers to "effective" uid + self.assertEqual(effective, os.getegid()) + # no such thing as os.getsuid() ("saved" uid), but starting + # from python 2.7 we have os.getresgid()[2] + if hasattr(os, "getresuid"): + self.assertEqual(saved, os.getresgid()[2]) + + def test_nice(self): + p = psutil.Process() + self.assertRaises(TypeError, p.nice, "str") + if WINDOWS: + try: + init = p.nice() + if sys.version_info > (3, 4): + self.assertIsInstance(init, enum.IntEnum) + else: + self.assertIsInstance(init, int) + self.assertEqual(init, psutil.NORMAL_PRIORITY_CLASS) + p.nice(psutil.HIGH_PRIORITY_CLASS) + self.assertEqual(p.nice(), psutil.HIGH_PRIORITY_CLASS) + p.nice(psutil.NORMAL_PRIORITY_CLASS) + self.assertEqual(p.nice(), psutil.NORMAL_PRIORITY_CLASS) + finally: + p.nice(psutil.NORMAL_PRIORITY_CLASS) + else: + try: + first_nice = p.nice() + p.nice(1) + self.assertEqual(p.nice(), 1) + # going back to previous nice value raises + # AccessDenied on OSX + if not OSX: + p.nice(0) + self.assertEqual(p.nice(), 0) + except psutil.AccessDenied: + pass + finally: + try: + p.nice(first_nice) + except psutil.AccessDenied: + pass + + def test_status(self): + p = psutil.Process() + self.assertEqual(p.status(), psutil.STATUS_RUNNING) + + def test_username(self): + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + if POSIX: + import pwd + self.assertEqual(p.username(), pwd.getpwuid(os.getuid()).pw_name) + with mock.patch("psutil.pwd.getpwuid", + side_effect=KeyError) as fun: + p.username() == str(p.uids().real) + assert fun.called + + elif WINDOWS and 'USERNAME' in os.environ: + expected_username = os.environ['USERNAME'] + expected_domain = os.environ['USERDOMAIN'] + domain, username = p.username().split('\\') + self.assertEqual(domain, expected_domain) + self.assertEqual(username, expected_username) + else: + p.username() + + def test_cwd(self): + sproc = get_test_subprocess(wait=True) + p = psutil.Process(sproc.pid) + self.assertEqual(p.cwd(), os.getcwd()) + + def test_cwd_2(self): + cmd = [PYTHON, "-c", "import os, time; os.chdir('..'); time.sleep(60)"] + sproc = get_test_subprocess(cmd, wait=True) + p = psutil.Process(sproc.pid) + call_until(p.cwd, "ret == os.path.dirname(os.getcwd())") + + @unittest.skipUnless(WINDOWS or LINUX or BSD, + 'not available on this platform') + @unittest.skipIf(LINUX and TRAVIS, "unknown failure on travis") + def test_cpu_affinity(self): + p = psutil.Process() + initial = p.cpu_affinity() + if hasattr(os, "sched_getaffinity"): + self.assertEqual(initial, list(os.sched_getaffinity(p.pid))) + self.assertEqual(len(initial), len(set(initial))) + all_cpus = list(range(len(psutil.cpu_percent(percpu=True)))) + # setting on travis doesn't seem to work (always return all + # CPUs on get): + # AssertionError: Lists differ: [0, 1, 2, 3, 4, 5, 6, ... != [0] + for n in all_cpus: + p.cpu_affinity([n]) + self.assertEqual(p.cpu_affinity(), [n]) + if hasattr(os, "sched_getaffinity"): + self.assertEqual(p.cpu_affinity(), + list(os.sched_getaffinity(p.pid))) + # + p.cpu_affinity(all_cpus) + self.assertEqual(p.cpu_affinity(), all_cpus) + if hasattr(os, "sched_getaffinity"): + self.assertEqual(p.cpu_affinity(), + list(os.sched_getaffinity(p.pid))) + # + self.assertRaises(TypeError, p.cpu_affinity, 1) + p.cpu_affinity(initial) + # it should work with all iterables, not only lists + p.cpu_affinity(set(all_cpus)) + p.cpu_affinity(tuple(all_cpus)) + invalid_cpu = [len(psutil.cpu_times(percpu=True)) + 10] + self.assertRaises(ValueError, p.cpu_affinity, invalid_cpu) + self.assertRaises(ValueError, p.cpu_affinity, range(10000, 11000)) + self.assertRaises(TypeError, p.cpu_affinity, [0, "1"]) + + # TODO + @unittest.skipIf(BSD, "broken on BSD, see #595") + @unittest.skipIf(APPVEYOR, + "can't find any process file on Appveyor") + def test_open_files(self): + # current process + p = psutil.Process() + files = p.open_files() + self.assertFalse(TESTFN in files) + with open(TESTFN, 'w'): + # give the kernel some time to see the new file + call_until(p.open_files, "len(ret) != %i" % len(files)) + filenames = [x.path for x in p.open_files()] + self.assertIn(TESTFN, filenames) + for file in filenames: + assert os.path.isfile(file), file + + # another process + cmdline = "import time; f = open(r'%s', 'r'); time.sleep(60);" % TESTFN + sproc = get_test_subprocess([PYTHON, "-c", cmdline], wait=True) + p = psutil.Process(sproc.pid) + + for x in range(100): + filenames = [x.path for x in p.open_files()] + if TESTFN in filenames: + break + time.sleep(.01) + else: + self.assertIn(TESTFN, filenames) + for file in filenames: + assert os.path.isfile(file), file + + # TODO + @unittest.skipIf(BSD, "broken on BSD, see #595") + @unittest.skipIf(APPVEYOR, + "can't find any process file on Appveyor") + def test_open_files2(self): + # test fd and path fields + with open(TESTFN, 'w') as fileobj: + p = psutil.Process() + for path, fd in p.open_files(): + if path == fileobj.name or fd == fileobj.fileno(): + break + else: + self.fail("no file found; files=%s" % repr(p.open_files())) + self.assertEqual(path, fileobj.name) + if WINDOWS: + self.assertEqual(fd, -1) + else: + self.assertEqual(fd, fileobj.fileno()) + # test positions + ntuple = p.open_files()[0] + self.assertEqual(ntuple[0], ntuple.path) + self.assertEqual(ntuple[1], ntuple.fd) + # test file is gone + self.assertTrue(fileobj.name not in p.open_files()) + + def compare_proc_sys_cons(self, pid, proc_cons): + from psutil._common import pconn + sys_cons = [] + for c in psutil.net_connections(kind='all'): + if c.pid == pid: + sys_cons.append(pconn(*c[:-1])) + if BSD: + # on BSD all fds are set to -1 + proc_cons = [pconn(*[-1] + list(x[1:])) for x in proc_cons] + self.assertEqual(sorted(proc_cons), sorted(sys_cons)) + + @skip_on_access_denied(only_if=OSX) + def test_connections(self): + def check_conn(proc, conn, family, type, laddr, raddr, status, kinds): + all_kinds = ("all", "inet", "inet4", "inet6", "tcp", "tcp4", + "tcp6", "udp", "udp4", "udp6") + check_connection_ntuple(conn) + self.assertEqual(conn.family, family) + self.assertEqual(conn.type, type) + self.assertEqual(conn.laddr, laddr) + self.assertEqual(conn.raddr, raddr) + self.assertEqual(conn.status, status) + for kind in all_kinds: + cons = proc.connections(kind=kind) + if kind in kinds: + self.assertNotEqual(cons, []) + else: + self.assertEqual(cons, []) + # compare against system-wide connections + # XXX Solaris can't retrieve system-wide UNIX + # sockets. + if not SUNOS: + self.compare_proc_sys_cons(proc.pid, [conn]) + + tcp_template = textwrap.dedent(""" + import socket, time + s = socket.socket($family, socket.SOCK_STREAM) + s.bind(('$addr', 0)) + s.listen(1) + with open('$testfn', 'w') as f: + f.write(str(s.getsockname()[:2])) + time.sleep(60) + """) + + udp_template = textwrap.dedent(""" + import socket, time + s = socket.socket($family, socket.SOCK_DGRAM) + s.bind(('$addr', 0)) + with open('$testfn', 'w') as f: + f.write(str(s.getsockname()[:2])) + time.sleep(60) + """) + + from string import Template + testfile = os.path.basename(TESTFN) + tcp4_template = Template(tcp_template).substitute( + family=int(AF_INET), addr="127.0.0.1", testfn=testfile) + udp4_template = Template(udp_template).substitute( + family=int(AF_INET), addr="127.0.0.1", testfn=testfile) + tcp6_template = Template(tcp_template).substitute( + family=int(AF_INET6), addr="::1", testfn=testfile) + udp6_template = Template(udp_template).substitute( + family=int(AF_INET6), addr="::1", testfn=testfile) + + # launch various subprocess instantiating a socket of various + # families and types to enrich psutil results + tcp4_proc = pyrun(tcp4_template) + tcp4_addr = eval(wait_for_file(testfile)) + udp4_proc = pyrun(udp4_template) + udp4_addr = eval(wait_for_file(testfile)) + if supports_ipv6(): + tcp6_proc = pyrun(tcp6_template) + tcp6_addr = eval(wait_for_file(testfile)) + udp6_proc = pyrun(udp6_template) + udp6_addr = eval(wait_for_file(testfile)) + else: + tcp6_proc = None + udp6_proc = None + tcp6_addr = None + udp6_addr = None + + for p in psutil.Process().children(): + cons = p.connections() + self.assertEqual(len(cons), 1) + for conn in cons: + # TCP v4 + if p.pid == tcp4_proc.pid: + check_conn(p, conn, AF_INET, SOCK_STREAM, tcp4_addr, (), + psutil.CONN_LISTEN, + ("all", "inet", "inet4", "tcp", "tcp4")) + # UDP v4 + elif p.pid == udp4_proc.pid: + check_conn(p, conn, AF_INET, SOCK_DGRAM, udp4_addr, (), + psutil.CONN_NONE, + ("all", "inet", "inet4", "udp", "udp4")) + # TCP v6 + elif p.pid == getattr(tcp6_proc, "pid", None): + check_conn(p, conn, AF_INET6, SOCK_STREAM, tcp6_addr, (), + psutil.CONN_LISTEN, + ("all", "inet", "inet6", "tcp", "tcp6")) + # UDP v6 + elif p.pid == getattr(udp6_proc, "pid", None): + check_conn(p, conn, AF_INET6, SOCK_DGRAM, udp6_addr, (), + psutil.CONN_NONE, + ("all", "inet", "inet6", "udp", "udp6")) + + @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), + 'AF_UNIX is not supported') + @skip_on_access_denied(only_if=OSX) + def test_connections_unix(self): + def check(type): + safe_remove(TESTFN) + sock = socket.socket(AF_UNIX, type) + with contextlib.closing(sock): + sock.bind(TESTFN) + cons = psutil.Process().connections(kind='unix') + conn = cons[0] + check_connection_ntuple(conn) + if conn.fd != -1: # != sunos and windows + self.assertEqual(conn.fd, sock.fileno()) + self.assertEqual(conn.family, AF_UNIX) + self.assertEqual(conn.type, type) + self.assertEqual(conn.laddr, TESTFN) + if not SUNOS: + # XXX Solaris can't retrieve system-wide UNIX + # sockets. + self.compare_proc_sys_cons(os.getpid(), cons) + + check(SOCK_STREAM) + check(SOCK_DGRAM) + + @unittest.skipUnless(hasattr(socket, "fromfd"), + 'socket.fromfd() is not availble') + @unittest.skipIf(WINDOWS or SUNOS, + 'connection fd not available on this platform') + def test_connection_fromfd(self): + with contextlib.closing(socket.socket()) as sock: + sock.bind(('localhost', 0)) + sock.listen(1) + p = psutil.Process() + for conn in p.connections(): + if conn.fd == sock.fileno(): + break + else: + self.fail("couldn't find socket fd") + dupsock = socket.fromfd(conn.fd, conn.family, conn.type) + with contextlib.closing(dupsock): + self.assertEqual(dupsock.getsockname(), conn.laddr) + self.assertNotEqual(sock.fileno(), dupsock.fileno()) + + def test_connection_constants(self): + ints = [] + strs = [] + for name in dir(psutil): + if name.startswith('CONN_'): + num = getattr(psutil, name) + str_ = str(num) + assert str_.isupper(), str_ + assert str_ not in strs, str_ + assert num not in ints, num + ints.append(num) + strs.append(str_) + if SUNOS: + psutil.CONN_IDLE + psutil.CONN_BOUND + if WINDOWS: + psutil.CONN_DELETE_TCB + + @unittest.skipUnless(POSIX, 'posix only') + def test_num_fds(self): + p = psutil.Process() + start = p.num_fds() + file = open(TESTFN, 'w') + self.addCleanup(file.close) + self.assertEqual(p.num_fds(), start + 1) + sock = socket.socket() + self.addCleanup(sock.close) + self.assertEqual(p.num_fds(), start + 2) + file.close() + sock.close() + self.assertEqual(p.num_fds(), start) + + @skip_on_not_implemented(only_if=LINUX) + def test_num_ctx_switches(self): + p = psutil.Process() + before = sum(p.num_ctx_switches()) + for x in range(500000): + after = sum(p.num_ctx_switches()) + if after > before: + return + self.fail("num ctx switches still the same after 50.000 iterations") + + def test_parent_ppid(self): + this_parent = os.getpid() + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + self.assertEqual(p.ppid(), this_parent) + self.assertEqual(p.parent().pid, this_parent) + # no other process is supposed to have us as parent + for p in psutil.process_iter(): + if p.pid == sproc.pid: + continue + self.assertTrue(p.ppid() != this_parent) + + def test_children(self): + p = psutil.Process() + self.assertEqual(p.children(), []) + self.assertEqual(p.children(recursive=True), []) + sproc = get_test_subprocess() + children1 = p.children() + children2 = p.children(recursive=True) + for children in (children1, children2): + self.assertEqual(len(children), 1) + self.assertEqual(children[0].pid, sproc.pid) + self.assertEqual(children[0].ppid(), os.getpid()) + + def test_children_recursive(self): + # here we create a subprocess which creates another one as in: + # A (parent) -> B (child) -> C (grandchild) + s = "import subprocess, os, sys, time;" + s += "PYTHON = os.path.realpath(sys.executable);" + s += "cmd = [PYTHON, '-c', 'import time; time.sleep(60);'];" + s += "subprocess.Popen(cmd);" + s += "time.sleep(60);" + get_test_subprocess(cmd=[PYTHON, "-c", s]) + p = psutil.Process() + self.assertEqual(len(p.children(recursive=False)), 1) + # give the grandchild some time to start + stop_at = time.time() + GLOBAL_TIMEOUT + while time.time() < stop_at: + children = p.children(recursive=True) + if len(children) > 1: + break + self.assertEqual(len(children), 2) + self.assertEqual(children[0].ppid(), os.getpid()) + self.assertEqual(children[1].ppid(), children[0].pid) + + def test_children_duplicates(self): + # find the process which has the highest number of children + table = collections.defaultdict(int) + for p in psutil.process_iter(): + try: + table[p.ppid()] += 1 + except psutil.Error: + pass + # this is the one, now let's make sure there are no duplicates + pid = sorted(table.items(), key=lambda x: x[1])[-1][0] + p = psutil.Process(pid) + try: + c = p.children(recursive=True) + except psutil.AccessDenied: # windows + pass + else: + self.assertEqual(len(c), len(set(c))) + + def test_suspend_resume(self): + sproc = get_test_subprocess(wait=True) + p = psutil.Process(sproc.pid) + p.suspend() + for x in range(100): + if p.status() == psutil.STATUS_STOPPED: + break + time.sleep(0.01) + p.resume() + self.assertNotEqual(p.status(), psutil.STATUS_STOPPED) + + def test_invalid_pid(self): + self.assertRaises(TypeError, psutil.Process, "1") + self.assertRaises(ValueError, psutil.Process, -1) + + def test_as_dict(self): + p = psutil.Process() + d = p.as_dict(attrs=['exe', 'name']) + self.assertEqual(sorted(d.keys()), ['exe', 'name']) + + p = psutil.Process(min(psutil.pids())) + d = p.as_dict(attrs=['connections'], ad_value='foo') + if not isinstance(d['connections'], list): + self.assertEqual(d['connections'], 'foo') + + def test_halfway_terminated_process(self): + # Test that NoSuchProcess exception gets raised in case the + # process dies after we create the Process object. + # Example: + # >>> proc = Process(1234) + # >>> time.sleep(2) # time-consuming task, process dies in meantime + # >>> proc.name() + # Refers to Issue #15 + sproc = get_test_subprocess() + p = psutil.Process(sproc.pid) + p.terminate() + p.wait() + if WINDOWS: + wait_for_pid(p.pid) + self.assertFalse(p.is_running()) + self.assertFalse(p.pid in psutil.pids()) + + excluded_names = ['pid', 'is_running', 'wait', 'create_time'] + if LINUX and not RLIMIT_SUPPORT: + excluded_names.append('rlimit') + for name in dir(p): + if (name.startswith('_') or + name in excluded_names): + continue + try: + meth = getattr(p, name) + # get/set methods + if name == 'nice': + if POSIX: + ret = meth(1) + else: + ret = meth(psutil.NORMAL_PRIORITY_CLASS) + elif name == 'ionice': + ret = meth() + ret = meth(2) + elif name == 'rlimit': + ret = meth(psutil.RLIMIT_NOFILE) + ret = meth(psutil.RLIMIT_NOFILE, (5, 5)) + elif name == 'cpu_affinity': + ret = meth() + ret = meth([0]) + elif name == 'send_signal': + ret = meth(signal.SIGTERM) + else: + ret = meth() + except psutil.ZombieProcess: + self.fail("ZombieProcess for %r was not supposed to happen" % + name) + except psutil.NoSuchProcess: + pass + except NotImplementedError: + pass + else: + self.fail( + "NoSuchProcess exception not raised for %r, retval=%s" % ( + name, ret)) + + @unittest.skipUnless(POSIX, 'posix only') + def test_zombie_process(self): + def succeed_or_zombie_p_exc(fun, *args, **kwargs): + try: + fun(*args, **kwargs) + except (psutil.ZombieProcess, psutil.AccessDenied): + pass + + # Note: in this test we'll be creating two sub processes. + # Both of them are supposed to be freed / killed by + # reap_children() as they are attributable to 'us' + # (os.getpid()) via children(recursive=True). + src = textwrap.dedent("""\ + import os, sys, time, socket, contextlib + child_pid = os.fork() + if child_pid > 0: + time.sleep(3000) + else: + # this is the zombie process + s = socket.socket(socket.AF_UNIX) + with contextlib.closing(s): + s.connect('%s') + if sys.version_info < (3, ): + pid = str(os.getpid()) + else: + pid = bytes(str(os.getpid()), 'ascii') + s.sendall(pid) + """ % TESTFN) + with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock: + try: + sock.settimeout(GLOBAL_TIMEOUT) + sock.bind(TESTFN) + sock.listen(1) + pyrun(src) + conn, _ = sock.accept() + select.select([conn.fileno()], [], [], GLOBAL_TIMEOUT) + zpid = int(conn.recv(1024)) + zproc = psutil.Process(zpid) + call_until(lambda: zproc.status(), + "ret == psutil.STATUS_ZOMBIE") + # A zombie process should always be instantiable + zproc = psutil.Process(zpid) + # ...and at least its status always be querable + self.assertEqual(zproc.status(), psutil.STATUS_ZOMBIE) + # ...and it should be considered 'running' + self.assertTrue(zproc.is_running()) + # ...and as_dict() shouldn't crash + zproc.as_dict() + if hasattr(zproc, "rlimit"): + succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE) + succeed_or_zombie_p_exc(zproc.rlimit, psutil.RLIMIT_NOFILE, + (5, 5)) + # set methods + succeed_or_zombie_p_exc(zproc.parent) + if hasattr(zproc, 'cpu_affinity'): + succeed_or_zombie_p_exc(zproc.cpu_affinity, [0]) + succeed_or_zombie_p_exc(zproc.nice, 0) + if hasattr(zproc, 'ionice'): + if LINUX: + succeed_or_zombie_p_exc(zproc.ionice, 2, 0) + else: + succeed_or_zombie_p_exc(zproc.ionice, 0) # Windows + if hasattr(zproc, 'rlimit'): + succeed_or_zombie_p_exc(zproc.rlimit, + psutil.RLIMIT_NOFILE, (5, 5)) + succeed_or_zombie_p_exc(zproc.suspend) + succeed_or_zombie_p_exc(zproc.resume) + succeed_or_zombie_p_exc(zproc.terminate) + succeed_or_zombie_p_exc(zproc.kill) + + # ...its parent should 'see' it + # edit: not true on BSD and OSX + # descendants = [x.pid for x in psutil.Process().children( + # recursive=True)] + # self.assertIn(zpid, descendants) + # XXX should we also assume ppid be usable? Note: this + # would be an important use case as the only way to get + # rid of a zombie is to kill its parent. + # self.assertEqual(zpid.ppid(), os.getpid()) + # ...and all other APIs should be able to deal with it + self.assertTrue(psutil.pid_exists(zpid)) + self.assertIn(zpid, psutil.pids()) + self.assertIn(zpid, [x.pid for x in psutil.process_iter()]) + psutil._pmap = {} + self.assertIn(zpid, [x.pid for x in psutil.process_iter()]) + finally: + reap_children(search_all=True) + + def test_pid_0(self): + # Process(0) is supposed to work on all platforms except Linux + if 0 not in psutil.pids(): + self.assertRaises(psutil.NoSuchProcess, psutil.Process, 0) + return + + p = psutil.Process(0) + self.assertTrue(p.name()) + + if POSIX: + try: + self.assertEqual(p.uids().real, 0) + self.assertEqual(p.gids().real, 0) + except psutil.AccessDenied: + pass + + self.assertRaisesRegexp( + ValueError, "preventing sending signal to process with PID 0", + p.send_signal, signal.SIGTERM) + + self.assertIn(p.ppid(), (0, 1)) + # self.assertEqual(p.exe(), "") + p.cmdline() + try: + p.num_threads() + except psutil.AccessDenied: + pass + + try: + p.memory_info() + except psutil.AccessDenied: + pass + + try: + if POSIX: + self.assertEqual(p.username(), 'root') + elif WINDOWS: + self.assertEqual(p.username(), 'NT AUTHORITY\\SYSTEM') + else: + p.username() + except psutil.AccessDenied: + pass + + self.assertIn(0, psutil.pids()) + self.assertTrue(psutil.pid_exists(0)) + + def test_Popen(self): + # Popen class test + # XXX this test causes a ResourceWarning on Python 3 because + # psutil.__subproc instance doesn't get propertly freed. + # Not sure what to do though. + cmd = [PYTHON, "-c", "import time; time.sleep(60);"] + proc = psutil.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + try: + proc.name() + proc.stdin + self.assertTrue(hasattr(proc, 'name')) + self.assertTrue(hasattr(proc, 'stdin')) + self.assertTrue(dir(proc)) + self.assertRaises(AttributeError, getattr, proc, 'foo') + finally: + proc.kill() + proc.wait() + self.assertIsNotNone(proc.returncode) + + +# =================================================================== +# --- Featch all processes test +# =================================================================== + +class TestFetchAllProcesses(unittest.TestCase): + """Test which iterates over all running processes and performs + some sanity checks against Process API's returned values. + """ + + def setUp(self): + if POSIX: + import pwd + pall = pwd.getpwall() + self._uids = set([x.pw_uid for x in pall]) + self._usernames = set([x.pw_name for x in pall]) + + def test_fetch_all(self): + valid_procs = 0 + excluded_names = set([ + 'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + 'as_dict', 'cpu_percent', 'parent', 'children', 'pid']) + if LINUX and not RLIMIT_SUPPORT: + excluded_names.add('rlimit') + attrs = [] + for name in dir(psutil.Process): + if name.startswith("_"): + continue + if name in excluded_names: + continue + attrs.append(name) + + default = object() + failures = [] + for name in attrs: + for p in psutil.process_iter(): + ret = default + try: + try: + args = () + attr = getattr(p, name, None) + if attr is not None and callable(attr): + if name == 'rlimit': + args = (psutil.RLIMIT_NOFILE,) + ret = attr(*args) + else: + ret = attr + valid_procs += 1 + except NotImplementedError: + msg = "%r was skipped because not implemented" % ( + self.__class__.__name__ + '.test_' + name) + warn(msg) + except (psutil.NoSuchProcess, psutil.AccessDenied) as err: + self.assertEqual(err.pid, p.pid) + if err.name: + # make sure exception's name attr is set + # with the actual process name + self.assertEqual(err.name, p.name()) + self.assertTrue(str(err)) + self.assertTrue(err.msg) + else: + if ret not in (0, 0.0, [], None, ''): + assert ret, ret + meth = getattr(self, name) + meth(ret) + except Exception as err: + s = '\n' + '=' * 70 + '\n' + s += "FAIL: test_%s (proc=%s" % (name, p) + if ret != default: + s += ", ret=%s)" % repr(ret) + s += ')\n' + s += '-' * 70 + s += "\n%s" % traceback.format_exc() + s = "\n".join((" " * 4) + i for i in s.splitlines()) + failures.append(s) + break + + if failures: + self.fail(''.join(failures)) + + # we should always have a non-empty list, not including PID 0 etc. + # special cases. + self.assertTrue(valid_procs > 0) + + def cmdline(self, ret): + pass + + def exe(self, ret): + if not ret: + self.assertEqual(ret, '') + else: + assert os.path.isabs(ret), ret + # Note: os.stat() may return False even if the file is there + # hence we skip the test, see: + # http://stackoverflow.com/questions/3112546/os-path-exists-lies + if POSIX and os.path.isfile(ret): + if hasattr(os, 'access') and hasattr(os, "X_OK"): + # XXX may fail on OSX + self.assertTrue(os.access(ret, os.X_OK)) + + def ppid(self, ret): + self.assertTrue(ret >= 0) + + def name(self, ret): + self.assertIsInstance(ret, (str, unicode)) + self.assertTrue(ret) + + def create_time(self, ret): + self.assertTrue(ret > 0) + # this can't be taken for granted on all platforms + # self.assertGreaterEqual(ret, psutil.boot_time()) + # make sure returned value can be pretty printed + # with strftime + time.strftime("%Y %m %d %H:%M:%S", time.localtime(ret)) + + def uids(self, ret): + for uid in ret: + self.assertTrue(uid >= 0) + self.assertIn(uid, self._uids) + + def gids(self, ret): + # note: testing all gids as above seems not to be reliable for + # gid == 30 (nodoby); not sure why. + for gid in ret: + self.assertTrue(gid >= 0) + # self.assertIn(uid, self.gids + + def username(self, ret): + self.assertTrue(ret) + if POSIX: + self.assertIn(ret, self._usernames) + + def status(self, ret): + self.assertTrue(ret != "") + self.assertTrue(ret != '?') + self.assertIn(ret, VALID_PROC_STATUSES) + + def io_counters(self, ret): + for field in ret: + if field != -1: + self.assertTrue(field >= 0) + + def ionice(self, ret): + if LINUX: + self.assertTrue(ret.ioclass >= 0) + self.assertTrue(ret.value >= 0) + else: + self.assertTrue(ret >= 0) + self.assertIn(ret, (0, 1, 2)) + + def num_threads(self, ret): + self.assertTrue(ret >= 1) + + def threads(self, ret): + for t in ret: + self.assertTrue(t.id >= 0) + self.assertTrue(t.user_time >= 0) + self.assertTrue(t.system_time >= 0) + + def cpu_times(self, ret): + self.assertTrue(ret.user >= 0) + self.assertTrue(ret.system >= 0) + + def memory_info(self, ret): + self.assertTrue(ret.rss >= 0) + self.assertTrue(ret.vms >= 0) + + def memory_info_ex(self, ret): + for name in ret._fields: + self.assertTrue(getattr(ret, name) >= 0) + if POSIX and ret.vms != 0: + # VMS is always supposed to be the highest + for name in ret._fields: + if name != 'vms': + value = getattr(ret, name) + assert ret.vms > value, ret + elif WINDOWS: + assert ret.peak_wset >= ret.wset, ret + assert ret.peak_paged_pool >= ret.paged_pool, ret + assert ret.peak_nonpaged_pool >= ret.nonpaged_pool, ret + assert ret.peak_pagefile >= ret.pagefile, ret + + def open_files(self, ret): + for f in ret: + if WINDOWS: + assert f.fd == -1, f + else: + self.assertIsInstance(f.fd, int) + assert os.path.isabs(f.path), f + assert os.path.isfile(f.path), f + + def num_fds(self, ret): + self.assertTrue(ret >= 0) + + def connections(self, ret): + self.assertEqual(len(ret), len(set(ret))) + for conn in ret: + check_connection_ntuple(conn) + + def cwd(self, ret): + if ret is not None: # BSD may return None + assert os.path.isabs(ret), ret + try: + st = os.stat(ret) + except OSError as err: + # directory has been removed in mean time + if err.errno != errno.ENOENT: + raise + else: + self.assertTrue(stat.S_ISDIR(st.st_mode)) + + def memory_percent(self, ret): + assert 0 <= ret <= 100, ret + + def is_running(self, ret): + self.assertTrue(ret) + + def cpu_affinity(self, ret): + assert ret != [], ret + + def terminal(self, ret): + if ret is not None: + assert os.path.isabs(ret), ret + assert os.path.exists(ret), ret + + def memory_maps(self, ret): + for nt in ret: + for fname in nt._fields: + value = getattr(nt, fname) + if fname == 'path': + if not value.startswith('['): + assert os.path.isabs(nt.path), nt.path + # commented as on Linux we might get + # '/foo/bar (deleted)' + # assert os.path.exists(nt.path), nt.path + elif fname in ('addr', 'perms'): + self.assertTrue(value) + else: + self.assertIsInstance(value, (int, long)) + assert value >= 0, value + + def num_handles(self, ret): + if WINDOWS: + self.assertGreaterEqual(ret, 0) + else: + self.assertGreaterEqual(ret, 0) + + def nice(self, ret): + if POSIX: + assert -20 <= ret <= 20, ret + else: + priorities = [getattr(psutil, x) for x in dir(psutil) + if x.endswith('_PRIORITY_CLASS')] + self.assertIn(ret, priorities) + + def num_ctx_switches(self, ret): + self.assertTrue(ret.voluntary >= 0) + self.assertTrue(ret.involuntary >= 0) + + def rlimit(self, ret): + self.assertEqual(len(ret), 2) + self.assertGreaterEqual(ret[0], -1) + self.assertGreaterEqual(ret[1], -1) + + +# =================================================================== +# --- Limited user tests +# =================================================================== + +@unittest.skipUnless(POSIX, "UNIX only") +@unittest.skipUnless(hasattr(os, 'getuid') and os.getuid() == 0, + "super user privileges are required") +class LimitedUserTestCase(TestProcess): + """Repeat the previous tests by using a limited user. + Executed only on UNIX and only if the user who run the test script + is root. + """ + # the uid/gid the test suite runs under + if hasattr(os, 'getuid'): + PROCESS_UID = os.getuid() + PROCESS_GID = os.getgid() + + def __init__(self, *args, **kwargs): + TestProcess.__init__(self, *args, **kwargs) + # re-define all existent test methods in order to + # ignore AccessDenied exceptions + for attr in [x for x in dir(self) if x.startswith('test')]: + meth = getattr(self, attr) + + def test_(self): + try: + meth() + except psutil.AccessDenied: + pass + setattr(self, attr, types.MethodType(test_, self)) + + def setUp(self): + safe_remove(TESTFN) + TestProcess.setUp(self) + os.setegid(1000) + os.seteuid(1000) + + def tearDown(self): + os.setegid(self.PROCESS_UID) + os.seteuid(self.PROCESS_GID) + TestProcess.tearDown(self) + + def test_nice(self): + try: + psutil.Process().nice(-1) + except psutil.AccessDenied: + pass + else: + self.fail("exception not raised") + + def test_zombie_process(self): + # causes problems if test test suite is run as root + pass + + +# =================================================================== +# --- Misc tests +# =================================================================== + +class TestMisc(unittest.TestCase): + """Misc / generic tests.""" + + def test_process__repr__(self, func=repr): + p = psutil.Process() + r = func(p) + self.assertIn("psutil.Process", r) + self.assertIn("pid=%s" % p.pid, r) + self.assertIn("name=", r) + self.assertIn(p.name(), r) + with mock.patch.object(psutil.Process, "name", + side_effect=psutil.ZombieProcess(os.getpid())): + p = psutil.Process() + r = func(p) + self.assertIn("pid=%s" % p.pid, r) + self.assertIn("zombie", r) + self.assertNotIn("name=", r) + with mock.patch.object(psutil.Process, "name", + side_effect=psutil.NoSuchProcess(os.getpid())): + p = psutil.Process() + r = func(p) + self.assertIn("pid=%s" % p.pid, r) + self.assertIn("terminated", r) + self.assertNotIn("name=", r) + + def test_process__str__(self): + self.test_process__repr__(func=str) + + def test_no_such_process__repr__(self, func=repr): + self.assertEqual( + repr(psutil.NoSuchProcess(321)), + "psutil.NoSuchProcess process no longer exists (pid=321)") + self.assertEqual( + repr(psutil.NoSuchProcess(321, name='foo')), + "psutil.NoSuchProcess process no longer exists (pid=321, " + "name='foo')") + self.assertEqual( + repr(psutil.NoSuchProcess(321, msg='foo')), + "psutil.NoSuchProcess foo") + + def test_zombie_process__repr__(self, func=repr): + self.assertEqual( + repr(psutil.ZombieProcess(321)), + "psutil.ZombieProcess process still exists but it's a zombie " + "(pid=321)") + self.assertEqual( + repr(psutil.ZombieProcess(321, name='foo')), + "psutil.ZombieProcess process still exists but it's a zombie " + "(pid=321, name='foo')") + self.assertEqual( + repr(psutil.ZombieProcess(321, name='foo', ppid=1)), + "psutil.ZombieProcess process still exists but it's a zombie " + "(pid=321, name='foo', ppid=1)") + self.assertEqual( + repr(psutil.ZombieProcess(321, msg='foo')), + "psutil.ZombieProcess foo") + + def test_access_denied__repr__(self, func=repr): + self.assertEqual( + repr(psutil.AccessDenied(321)), + "psutil.AccessDenied (pid=321)") + self.assertEqual( + repr(psutil.AccessDenied(321, name='foo')), + "psutil.AccessDenied (pid=321, name='foo')") + self.assertEqual( + repr(psutil.AccessDenied(321, msg='foo')), + "psutil.AccessDenied foo") + + def test_timeout_expired__repr__(self, func=repr): + self.assertEqual( + repr(psutil.TimeoutExpired(321)), + "psutil.TimeoutExpired timeout after 321 seconds") + self.assertEqual( + repr(psutil.TimeoutExpired(321, pid=111)), + "psutil.TimeoutExpired timeout after 321 seconds (pid=111)") + self.assertEqual( + repr(psutil.TimeoutExpired(321, pid=111, name='foo')), + "psutil.TimeoutExpired timeout after 321 seconds " + "(pid=111, name='foo')") + + def test_process__eq__(self): + p1 = psutil.Process() + p2 = psutil.Process() + self.assertEqual(p1, p2) + p2._ident = (0, 0) + self.assertNotEqual(p1, p2) + self.assertNotEqual(p1, 'foo') + + def test_process__hash__(self): + s = set([psutil.Process(), psutil.Process()]) + self.assertEqual(len(s), 1) + + def test__all__(self): + dir_psutil = dir(psutil) + for name in dir_psutil: + if name in ('callable', 'error', 'namedtuple', + 'long', 'test', 'NUM_CPUS', 'BOOT_TIME', + 'TOTAL_PHYMEM'): + continue + if not name.startswith('_'): + try: + __import__(name) + except ImportError: + if name not in psutil.__all__: + fun = getattr(psutil, name) + if fun is None: + continue + if (fun.__doc__ is not None and + 'deprecated' not in fun.__doc__.lower()): + self.fail('%r not in psutil.__all__' % name) + + # Import 'star' will break if __all__ is inconsistent, see: + # https://github.com/giampaolo/psutil/issues/656 + # Can't do `from psutil import *` as it won't work on python 3 + # so we simply iterate over __all__. + for name in psutil.__all__: + self.assertIn(name, dir_psutil) + + def test_version(self): + self.assertEqual('.'.join([str(x) for x in psutil.version_info]), + psutil.__version__) + + def test_memoize(self): + from psutil._common import memoize + + @memoize + def foo(*args, **kwargs): + "foo docstring" + calls.append(None) + return (args, kwargs) + + calls = [] + # no args + for x in range(2): + ret = foo() + expected = ((), {}) + self.assertEqual(ret, expected) + self.assertEqual(len(calls), 1) + # with args + for x in range(2): + ret = foo(1) + expected = ((1, ), {}) + self.assertEqual(ret, expected) + self.assertEqual(len(calls), 2) + # with args + kwargs + for x in range(2): + ret = foo(1, bar=2) + expected = ((1, ), {'bar': 2}) + self.assertEqual(ret, expected) + self.assertEqual(len(calls), 3) + # clear cache + foo.cache_clear() + ret = foo() + expected = ((), {}) + self.assertEqual(ret, expected) + self.assertEqual(len(calls), 4) + # docstring + self.assertEqual(foo.__doc__, "foo docstring") + + def test_isfile_strict(self): + from psutil._common import isfile_strict + this_file = os.path.abspath(__file__) + assert isfile_strict(this_file) + assert not isfile_strict(os.path.dirname(this_file)) + with mock.patch('psutil._common.os.stat', + side_effect=OSError(errno.EPERM, "foo")): + self.assertRaises(OSError, isfile_strict, this_file) + with mock.patch('psutil._common.os.stat', + side_effect=OSError(errno.EACCES, "foo")): + self.assertRaises(OSError, isfile_strict, this_file) + with mock.patch('psutil._common.os.stat', + side_effect=OSError(errno.EINVAL, "foo")): + assert not isfile_strict(this_file) + with mock.patch('psutil._common.stat.S_ISREG', return_value=False): + assert not isfile_strict(this_file) + + def test_serialization(self): + def check(ret): + if json is not None: + json.loads(json.dumps(ret)) + a = pickle.dumps(ret) + b = pickle.loads(a) + self.assertEqual(ret, b) + + check(psutil.Process().as_dict()) + check(psutil.virtual_memory()) + check(psutil.swap_memory()) + check(psutil.cpu_times()) + check(psutil.cpu_times_percent(interval=0)) + check(psutil.net_io_counters()) + if LINUX and not os.path.exists('/proc/diskstats'): + pass + else: + if not APPVEYOR: + check(psutil.disk_io_counters()) + check(psutil.disk_partitions()) + check(psutil.disk_usage(os.getcwd())) + check(psutil.users()) + + def test_setup_script(self): + here = os.path.abspath(os.path.dirname(__file__)) + setup_py = os.path.realpath(os.path.join(here, '..', 'setup.py')) + module = imp.load_source('setup', setup_py) + self.assertRaises(SystemExit, module.setup) + self.assertEqual(module.get_version(), psutil.__version__) + + def test_ad_on_process_creation(self): + # We are supposed to be able to instantiate Process also in case + # of zombie processes or access denied. + with mock.patch.object(psutil.Process, 'create_time', + side_effect=psutil.AccessDenied) as meth: + psutil.Process() + assert meth.called + with mock.patch.object(psutil.Process, 'create_time', + side_effect=psutil.ZombieProcess(1)) as meth: + psutil.Process() + assert meth.called + with mock.patch.object(psutil.Process, 'create_time', + side_effect=ValueError) as meth: + with self.assertRaises(ValueError): + psutil.Process() + assert meth.called + + +# =================================================================== +# --- Example script tests +# =================================================================== + +class TestExampleScripts(unittest.TestCase): + """Tests for scripts in the examples directory.""" + + def assert_stdout(self, exe, args=None): + exe = os.path.join(EXAMPLES_DIR, exe) + if args: + exe = exe + ' ' + args + try: + out = sh(sys.executable + ' ' + exe).strip() + except RuntimeError as err: + if 'AccessDenied' in str(err): + return str(err) + else: + raise + assert out, out + return out + + def assert_syntax(self, exe, args=None): + exe = os.path.join(EXAMPLES_DIR, exe) + with open(exe, 'r') as f: + src = f.read() + ast.parse(src) + + def test_check_presence(self): + # make sure all example scripts have a test method defined + meths = dir(self) + for name in os.listdir(EXAMPLES_DIR): + if name.endswith('.py'): + if 'test_' + os.path.splitext(name)[0] not in meths: + # self.assert_stdout(name) + self.fail('no test defined for %r script' + % os.path.join(EXAMPLES_DIR, name)) + + def test_disk_usage(self): + self.assert_stdout('disk_usage.py') + + def test_free(self): + self.assert_stdout('free.py') + + def test_meminfo(self): + self.assert_stdout('meminfo.py') + + def test_process_detail(self): + self.assert_stdout('process_detail.py') + + @unittest.skipIf(APPVEYOR, "can't find users on Appveyor") + def test_who(self): + self.assert_stdout('who.py') + + def test_ps(self): + self.assert_stdout('ps.py') + + def test_pstree(self): + self.assert_stdout('pstree.py') + + def test_netstat(self): + self.assert_stdout('netstat.py') + + @unittest.skipIf(TRAVIS, "permission denied on travis") + def test_ifconfig(self): + self.assert_stdout('ifconfig.py') + + def test_pmap(self): + self.assert_stdout('pmap.py', args=str(os.getpid())) + + @unittest.skipIf(ast is None, + 'ast module not available on this python version') + def test_killall(self): + self.assert_syntax('killall.py') + + @unittest.skipIf(ast is None, + 'ast module not available on this python version') + def test_nettop(self): + self.assert_syntax('nettop.py') + + @unittest.skipIf(ast is None, + 'ast module not available on this python version') + def test_top(self): + self.assert_syntax('top.py') + + @unittest.skipIf(ast is None, + 'ast module not available on this python version') + def test_iotop(self): + self.assert_syntax('iotop.py') + + def test_pidof(self): + output = self.assert_stdout('pidof.py %s' % psutil.Process().name()) + self.assertIn(str(os.getpid()), output) + + +def main(): + tests = [] + test_suite = unittest.TestSuite() + tests.append(TestSystemAPIs) + tests.append(TestProcess) + tests.append(TestFetchAllProcesses) + tests.append(TestMisc) + tests.append(TestExampleScripts) + tests.append(LimitedUserTestCase) + + if POSIX: + from _posix import PosixSpecificTestCase + tests.append(PosixSpecificTestCase) + + # import the specific platform test suite + stc = None + if LINUX: + from _linux import LinuxSpecificTestCase as stc + elif WINDOWS: + from _windows import WindowsSpecificTestCase as stc + from _windows import TestDualProcessImplementation + tests.append(TestDualProcessImplementation) + elif OSX: + from _osx import OSXSpecificTestCase as stc + elif BSD: + from _bsd import BSDSpecificTestCase as stc + elif SUNOS: + from _sunos import SunOSSpecificTestCase as stc + if stc is not None: + tests.append(stc) + + for test_class in tests: + test_suite.addTest(unittest.makeSuite(test_class)) + result = unittest.TextTestRunner(verbosity=2).run(test_suite) + return result.wasSuccessful() + +if __name__ == '__main__': + if not main(): + sys.exit(1) diff --git a/python/psutil/tox.ini b/python/psutil/tox.ini new file mode 100644 index 0000000000..d80dd174b8 --- /dev/null +++ b/python/psutil/tox.ini @@ -0,0 +1,32 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. +# To use it run "pip install tox" and then run "tox" from this +# directory. + +[tox] +envlist = py26, py27, py32, py33, py34 + +[testenv] +deps = + flake8 + pytest + py26: ipaddress + py26: mock==1.0.1 + py26: unittest2 + py27: ipaddress + py27: mock + py32: ipaddress + py32: mock + py33: ipaddress + +setenv = + PYTHONPATH = {toxinidir}/test + +commands = + py.test {posargs} + git ls-files | grep \\.py$ | xargs flake8 + +# suppress "WARNING: 'git' command found but not installed in testenv +whitelist_externals = git +usedevelop = True |