diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..5e86f6b --- /dev/null +++ b/MANIFEST @@ -0,0 +1,10 @@ +MANIFEST +COPYING +Makefile +pflags-cmd.py +procfs/__init__.py +procfs/procfs.py +procfs/sysctl.py +procfs/utilist.py +rpm/SPECS/python-linux-procfs.spec +setup.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ebca02d --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +PACKAGE := python-linux-procfs +VERSION := $(shell rpm -q --qf '%{VERSION}' --specfile rpm/SPECS/$(PACKAGE).spec) + +rpmdirs: + @[ -d rpm/BUILD ] || mkdir rpm/BUILD + @[ -d rpm/RPMS ] || mkdir rpm/RPMS + @[ -d rpm/SRPMS ] || mkdir rpm/SRPMS + @[ -d rpm/SOURCES ] || mkdir rpm/SOURCES + +bz2: rpmdirs + git archive --format=tar --prefix=$(PACKAGE)-$(VERSION)/ HEAD | \ + bzip2 -9 > rpm/SOURCES/$(PACKAGE)-$(VERSION).tar.bz2 + +rpm: bz2 rpmdirs + rpmbuild -ba --define "_topdir $(PWD)/rpm" rpm/SPECS/$(PACKAGE).spec + +bz2dev: rpmdirs + @mkdir -p /tmp/$(PACKAGE)-$(VERSION) + @tar cf - `cat MANIFEST` | (cd /tmp/$(PACKAGE)-$(VERSION) ; tar xf -) + @(cd /tmp; tar cf - $(PACKAGE)-$(VERSION)) | bzip2 -9 > rpm/SOURCES/$(PACKAGE)-$(VERSION).tar.bz2 + +rpmdev: bz2dev rpmdirs + rpmbuild -ba --define "_topdir $(PWD)/rpm" rpm/SPECS/$(PACKAGE).spec + +rpmclean: + @rm -f rpm/RPMS/*/$(PACKAGE)-$(VERSION)-*.rpm + @rm -f rpm/SRPMS/$(PACKAGE)-$(VERSION)-*.src.rpm + @rm -f rpm/SOURCES/$(PACKAGE)-$(VERSION).tar.bz2 + @rm -rf rpm/BUILD/$(PACKAGE)-$(VERSION)* + +pyclean: + @find . -type f \( -name \*~ -o -name \*.pyc \) -delete + +clean: pyclean rpmclean diff --git a/bitmasklist_test.py b/bitmasklist_test.py new file mode 100755 index 0000000..60a8da3 --- /dev/null +++ b/bitmasklist_test.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +from __future__ import print_function +from procfs import bitmasklist + +class bitmasklist_test: + # Assume true (passed) until proven false + # Many tests can be run, but just one failure is recorded overall here + unit_test_result = 0; # Assume true (passed) until proven false + + def __init__(self, line, nr_entries, expected_result): + self.result = 0; # Assume pass + self.line = line + self.nr_entries = nr_entries # Corresponds to the number of cpus + self.expected_result = expected_result + + # A failure in any single test is recorded as an overall failure + def set_unit_test_result(self): + if bitmasklist_test.unit_test_result == 1: + return + if self.result == 1: + bitmasklist_test.unit_test_result = 1 + return + + # This is the function that actually runs the test + def bitmasklist_test(self): + print("\n##################\n") + cpu = bitmasklist(self.line, self.nr_entries) + print("Converted : ", self.line, "\nto ", cpu) + if cpu == self.expected_result: + self.result = 0 + print("PASS") + else: + self.result = 1 + print("expected : ", self.expected_result) + print("FAIL") + self.set_unit_test_result() + +# CPU 2 +t = \ + bitmasklist_test("00000000,00000000,00000000,00000000,00000000,00000004", 44, [2]) +t.bitmasklist_test() + +# CPU 34 +t = \ + bitmasklist_test("00000000,00000000,00000000,00000000,00000004,00000000", 44, [34]) +t.bitmasklist_test() + +# CPU 30 +t = \ + bitmasklist_test("00000000,00000000,00000000,00000000,00000000,40000000", 44, [30]) +t.bitmasklist_test() + +# CPU 0, 32 +t = \ + bitmasklist_test("00000000,00000000,00000000,00000000,00000001,00000001", 44, [0,32]) +t.bitmasklist_test() + +# cpu 0-15 +t = \ + bitmasklist_test("ffff", 44, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]) +t.bitmasklist_test() + +#cpu 0-71 +t = \ + bitmasklist_test("ff,ffffffff,ffffffff", 96, [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71]) +t.bitmasklist_test() + +exit(bitmasklist_test.unit_test_result) diff --git a/pflags b/pflags new file mode 100755 index 0000000..abfcfe9 --- /dev/null +++ b/pflags @@ -0,0 +1,68 @@ +#! /usr/bin/python +# -*- python -*- +# -*- coding: utf-8 -*- +# print process flags +# Copyright (C) 2015 Red Hat Inc. +# Arnaldo Carvalho de Melo +# +# This application is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; version 2. +# +# This application is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +from __future__ import print_function +import procfs, re, fnmatch, sys +from functools import reduce +from six.moves import map + +ps = None + +def thread_mapper(s): + global ps + + try: + return [ int(s), ] + except: + pass + try: + return ps.find_by_regex(re.compile(fnmatch.translate(s))) + except: + return ps.find_by_name(s) + +def main(argv): + + global ps + ps = procfs.pidstats() + + if (len(argv) > 1): + pids = reduce(lambda i, j: i + j, list(map(thread_mapper, argv[1].split(",")))) + else: + pids = list(ps.processes.keys()) + + pids.sort() + len_comms = [len(ps[pid]["stat"]["comm"]) for pid in pids] + max_comm_len = max(len_comms) + del(len_comms) + + for pid in pids: + flags = ps[pid].stat.process_flags() + # Remove flags that were superseeded + if "PF_THREAD_BOUND" in flags and "PF_NO_SETAFFINITY" in flags: + flags.remove("PF_THREAD_BOUND") + if "PF_FLUSHER" in flags and "PF_NPROC_EXCEEDED" in flags: + flags.remove("PF_FLUSHER") + if "PF_SWAPOFF" in flags and "PF_MEMALLOC_NOIO" in flags: + flags.remove("PF_SWAPOFF") + if "PF_FREEZER_NOSIG" in flags and "PF_SUSPEND_TASK" in flags: + flags.remove("PF_FREEZER_NOSIG") + comm = ps[pid].stat["comm"] + flags.sort() + sflags = reduce(lambda i, j: "%s|%s" % (i, j), [a[3:] for a in flags]) + print("%6d %*s %s" %(pid, max_comm_len, comm, sflags)) + +if __name__ == '__main__': + main(sys.argv) diff --git a/procfs/__init__.py b/procfs/__init__.py new file mode 100644 index 0000000..7fd67ae --- /dev/null +++ b/procfs/__init__.py @@ -0,0 +1,32 @@ +#! /usr/bin/python +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Copyright (C) 2008, 2009 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +""" +Copyright (c) 2008, 2009 Red Hat Inc. + +Abstractions to extract information from the Linux kernel /proc files. +""" +from __future__ import absolute_import + +__author__ = "Arnaldo Carvalho de Melo " +__license__ = "GPLv2 License" + +from .procfs import * +from .sysctl import * +from .utilist import * diff --git a/procfs/procfs.py b/procfs/procfs.py new file mode 100755 index 0000000..c70fe2a --- /dev/null +++ b/procfs/procfs.py @@ -0,0 +1,1069 @@ +#! /usr/bin/python +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2015 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +from __future__ import absolute_import +from __future__ import print_function +import os, time +from .utilist import bitmasklist +from six.moves import range + +VERSION="0.5" + +def process_cmdline(pid_info): + """ + Returns the process command line, if available in the given `process' class, if + not available, falls back to using the comm (short process name) in its pidstat key. + """ + if pid_info["cmdline"]: + return reduce(lambda a, b: a + " %s" % b, pid_info["cmdline"]).strip() + + return pid_info["stat"]["comm"] + +class pidstat: + """Provides a dictionary to access the fields in the per process /proc/PID/stat + files. + + One can obtain the available fields asking for the keys of the dictionary, e.g.: + + >>> p = procfs.pidstat(1) + >>> print p.keys() + ['majflt', 'rss', 'cnswap', 'cstime', 'pid', 'session', 'startstack', 'startcode', 'cmajflt', 'blocked', 'exit_signal', 'minflt', 'nswap', 'environ', 'priority', 'state', 'delayacct_blkio_ticks', 'policy', 'rt_priority', 'ppid', 'nice', 'cutime', 'endcode', 'wchan', 'num_threads', 'sigcatch', 'comm', 'stime', 'sigignore', 'tty_nr', 'kstkeip', 'utime', 'tpgid', 'itrealvalue', 'kstkesp', 'rlim', 'signal', 'pgrp', 'flags', 'starttime', 'cminflt', 'vsize', 'processor'] + + And then access the various process properties using it as a dictionary: + + >>> print p['comm'] + systemd + >>> print p['priority'] + 20 + >>> print p['state'] + S + + Please refer to the 'procfs(5)' man page, by using: + + $ man 5 procfs + + To see information for each of the above fields, it is part of the + 'man-pages' RPM package. + """ + + # Entries with the same value, the one with a comment after it is the + # more recent, having replaced the other name in v4.1-rc kernel times. + + PF_ALIGNWARN = 0x00000001 + PF_STARTING = 0x00000002 + PF_EXITING = 0x00000004 + PF_EXITPIDONE = 0x00000008 + PF_VCPU = 0x00000010 + PF_WQ_WORKER = 0x00000020 # /* I'm a workqueue worker */ + PF_FORKNOEXEC = 0x00000040 + PF_MCE_PROCESS = 0x00000080 # /* process policy on mce errors */ + PF_SUPERPRIV = 0x00000100 + PF_DUMPCORE = 0x00000200 + PF_SIGNALED = 0x00000400 + PF_MEMALLOC = 0x00000800 + PF_NPROC_EXCEEDED= 0x00001000 # /* set_user noticed that RLIMIT_NPROC was exceeded */ + PF_FLUSHER = 0x00001000 + PF_USED_MATH = 0x00002000 + PF_USED_ASYNC = 0x00004000 # /* used async_schedule*(), used by module init */ + PF_NOFREEZE = 0x00008000 + PF_FROZEN = 0x00010000 + PF_FSTRANS = 0x00020000 + PF_KSWAPD = 0x00040000 + PF_MEMALLOC_NOIO = 0x00080000 # /* Allocating memory without IO involved */ + PF_SWAPOFF = 0x00080000 + PF_LESS_THROTTLE = 0x00100000 + PF_KTHREAD = 0x00200000 + PF_RANDOMIZE = 0x00400000 + PF_SWAPWRITE = 0x00800000 + PF_SPREAD_PAGE = 0x01000000 + PF_SPREAD_SLAB = 0x02000000 + PF_THREAD_BOUND = 0x04000000 + PF_NO_SETAFFINITY = 0x04000000 # /* Userland is not allowed to meddle with cpus_allowed */ + PF_MCE_EARLY = 0x08000000 # /* Early kill for mce process policy */ + PF_MEMPOLICY = 0x10000000 + PF_MUTEX_TESTER = 0x20000000 + PF_FREEZER_SKIP = 0x40000000 + PF_FREEZER_NOSIG = 0x80000000 + PF_SUSPEND_TASK = 0x80000000 # /* this thread called freeze_processes and should not be frozen */ + + proc_stat_fields = [ "pid", "comm", "state", "ppid", "pgrp", "session", + "tty_nr", "tpgid", "flags", "minflt", "cminflt", + "majflt", "cmajflt", "utime", "stime", "cutime", + "cstime", "priority", "nice", "num_threads", + "itrealvalue", "starttime", "vsize", "rss", + "rlim", "startcode", "endcode", "startstack", + "kstkesp", "kstkeip", "signal", "blocked", + "sigignore", "sigcatch", "wchan", "nswap", + "cnswap", "exit_signal", "processor", + "rt_priority", "policy", + "delayacct_blkio_ticks", "environ" ] + + def __init__(self, pid, basedir = "/proc"): + self.pid = pid + self.load(basedir) + + def __getitem__(self, fieldname): + return self.fields[fieldname] + + def keys(self): + return list(self.fields.keys()) + + def values(self): + return list(self.fields.values()) + + def has_key(self, fieldname): + return fieldname in self.fields + + def items(self): + return self.fields + + def __contains__(self, fieldname): + return fieldname in self.fields + + def load(self, basedir = "/proc"): + f = open("%s/%d/stat" % (basedir, self.pid)) + fields = f.readline().strip().split(') ') + f.close() + fields = fields[0].split(' (') + fields[1].split() + self.fields = {} + nr_fields = min(len(fields), len(self.proc_stat_fields)) + for i in range(nr_fields): + attrname = self.proc_stat_fields[i] + value = fields[i] + if attrname == "comm": + self.fields["comm"] = value.strip('()') + else: + try: + self.fields[attrname] = int(value) + except: + self.fields[attrname] = value + + def is_bound_to_cpu(self): + """ + Returns true if this process has a fixed smp affinity mask, + not allowing it to be moved to a different set of CPUs. + """ + return self.fields["flags"] & self.PF_THREAD_BOUND and \ + True or False + + def process_flags(self): + """ + Returns a list with all the process flags known, details depend + on kernel version, declared in the file include/linux/sched.h in + the kernel sources. + + As of v4.2-rc7 these include (from include/linux/sched.h comments): + + PF_EXITING Getting shut down + PF_EXITPIDONE Pi exit done on shut down + PF_VCPU I'm a virtual CPU + PF_WQ_WORKER I'm a workqueue worker + PF_FORKNOEXEC Forked but didn't exec + PF_MCE_PROCESS Process policy on mce errors + PF_SUPERPRIV Used super-user privileges + PF_DUMPCORE Dumped core + PF_SIGNALED Killed by a signal + PF_MEMALLOC Allocating memory + PF_NPROC_EXCEEDED Set_user noticed that RLIMIT_NPROC was exceeded + PF_USED_MATH If unset the fpu must be initialized before use + PF_USED_ASYNC Used async_schedule*(), used by module init + PF_NOFREEZE This thread should not be frozen + PF_FROZEN Frozen for system suspend + PF_FSTRANS Inside a filesystem transaction + PF_KSWAPD I am kswapd + PF_MEMALLOC_NOIO Allocating memory without IO involved + PF_LESS_THROTTLE Throttle me less: I clean memory + PF_KTHREAD I am a kernel thread + PF_RANDOMIZE Randomize virtual address space + PF_SWAPWRITE Allowed to write to swap + PF_NO_SETAFFINITY Userland is not allowed to meddle with cpus_allowed + PF_MCE_EARLY Early kill for mce process policy + PF_MUTEX_TESTER Thread belongs to the rt mutex tester + PF_FREEZER_SKIP Freezer should not count it as freezable + PF_SUSPEND_TASK This thread called freeze_processes and should not be frozen + + """ + sflags = [] + for attr in dir(self): + if attr[:3] != "PF_": + continue + value = getattr(self, attr) + if value & self.fields["flags"]: + sflags.append(attr) + + return sflags + +def cannot_set_affinity(self, pid): + PF_NO_SETAFFINITY = 0x04000000 + try: + return int(self.processes[pid]["stat"]["flags"]) & \ + PF_NO_SETAFFINITY and True or False + except: + return True + +def cannot_set_thread_affinity(self, pid, tid): + PF_NO_SETAFFINITY = 0x04000000 + try: + return int(self.processes[pid].threads[tid]["stat"]["flags"]) & \ + PF_NO_SETAFFINITY and True or False + except: + return True + +class pidstatus: + """ + Provides a dictionary to access the fields in the per process /proc/PID/status + files. This provides additional information about processes and threads to what + can be obtained with the procfs.pidstat() class. + + One can obtain the available fields asking for the keys of the dictionary, e.g.: + + >>> import procfs + >>> p = procfs.pidstatus(1) + >>> print p.keys() + ['VmExe', 'CapBnd', 'NSpgid', 'Tgid', 'NSpid', 'VmSize', 'VmPMD', 'ShdPnd', 'State', 'Gid', 'nonvoluntary_ctxt_switches', 'SigIgn', 'VmStk', 'VmData', 'SigCgt', 'CapEff', 'VmPTE', 'Groups', 'NStgid', 'Threads', 'PPid', 'VmHWM', 'NSsid', 'VmSwap', 'Name', 'SigBlk', 'Mems_allowed_list', 'VmPeak', 'Ngid', 'VmLck', 'SigQ', 'VmPin', 'Mems_allowed', 'CapPrm', 'Seccomp', 'VmLib', 'Cpus_allowed', 'Uid', 'SigPnd', 'Pid', 'Cpus_allowed_list', 'TracerPid', 'CapInh', 'voluntary_ctxt_switches', 'VmRSS', 'FDSize'] + >>> print p["Pid"] + 1 + >>> print p["Threads"] + 1 + >>> print p["VmExe"] + 1248 kB + >>> print p["Cpus_allowed"] + f + >>> print p["SigQ"] + 0/30698 + >>> print p["VmPeak"] + 320300 kB + >>> + + Please refer to the 'procfs(5)' man page, by using: + + $ man 5 procfs + + To see information for each of the above fields, it is part of the + 'man-pages' RPM package. + + In the man page there will be references to further documentation, like + referring to the "getrlimit(2)" man page when explaining the "SigQ" + line/field. + """ + + def __init__(self, pid, basedir = "/proc"): + self.pid = pid + self.load(basedir) + + def __getitem__(self, fieldname): + return self.fields[fieldname] + + def keys(self): + return list(self.fields.keys()) + + def values(self): + return list(self.fields.values()) + + def has_key(self, fieldname): + return fieldname in self.fields + + def items(self): + return self.fields + + def __contains__(self, fieldname): + return fieldname in self.fields + + def load(self, basedir = "/proc"): + f = open("%s/%d/status" % (basedir, self.pid)) + self.fields = {} + for line in f.readlines(): + fields = line.split(":") + if len(fields) != 2: + continue + name = fields[0] + value = fields[1].strip() + try: + self.fields[name] = int(value) + except: + self.fields[name] = value + f.close() + +class process: + """ + Information about a process with a given pid, provides a dictionary with + two entries, instances of different wrappers for /proc/ process related + meta files: "stat" and "status", see the documentation for procfs.pidstat + and procfs.pidstatus for further info about those classes. + """ + + def __init__(self, pid, basedir = "/proc"): + self.pid = pid + self.basedir = basedir + + def __getitem__(self, attr): + if not hasattr(self, attr): + if attr in ("stat", "status"): + if attr == "stat": + sclass = pidstat + else: + sclass = pidstatus + + setattr(self, attr, sclass(self.pid, self.basedir)) + elif attr == "cmdline": + self.load_cmdline() + elif attr == "threads": + self.load_threads() + elif attr == "cgroups": + self.load_cgroups() + elif attr == "environ": + self.load_environ() + + return getattr(self, attr) + + def has_key(self, attr): + return hasattr(self, attr) + + def __contains__(self, attr): + return hasattr(self, attr) + + def load_cmdline(self): + f = open("/proc/%d/cmdline" % self.pid) + self.cmdline = f.readline().strip().split('\0')[:-1] + f.close() + + def load_threads(self): + self.threads = pidstats("/proc/%d/task/" % self.pid) + # remove thread leader + del self.threads[self.pid] + + def load_cgroups(self): + f = open("/proc/%d/cgroup" % self.pid) + self.cgroups = "" + for line in reversed(f.readlines()): + if len(self.cgroups): + self.cgroups = self.cgroups + "," + line[:-1] + else: + self.cgroups = line[:-1] + f.close() + + def load_environ(self): + """ + Loads the environment variables for this process. The entries then + become available via the 'environ' member, or via the 'environ' + dict key when accessing as p["environ"]. + + E.g.: + + + >>> all_processes = procfs.pidstats() + >>> firefox_pid = all_processes.find_by_name("firefox") + >>> firefox_process = all_processes[firefox_pid[0]] + >>> print firefox_process["environ"]["PWD"] + /home/acme + >>> print len(firefox_process.environ.keys()) + 66 + >>> print firefox_process["environ"]["SHELL"] + /bin/bash + >>> print firefox_process["environ"]["USERNAME"] + acme + >>> print firefox_process["environ"]["HOME"] + /home/acme + >>> print firefox_process["environ"]["MAIL"] + /var/spool/mail/acme + >>> + """ + self.environ = {} + f = open("/proc/%d/environ" % self.pid) + for x in f.readline().split('\0'): + if len(x) > 0: + y = x.split('=') + self.environ[y[0]] = y[1] + f.close() + +class pidstats: + """ + Provides access to all the processes in the system, to get a picture of + how many processes there are at any given moment. + + The entries can be accessed as a dictionary, keyed by pid. Also there are + methods to find processes that match a given COMM or regular expression. + """ + def __init__(self, basedir = "/proc"): + self.basedir = basedir + self.processes = {} + self.reload() + + def __getitem__(self, key): + return self.processes[key] + + def __delitem__(self, key): + # not clear on why this can fail, but it can + try: + del self.processes[key] + except: + pass + + def keys(self): + return list(self.processes.keys()) + + def values(self): + return list(self.processes.values()) + + def has_key(self, key): + return key in self.processes + + def items(self): + return self.processes + + def __contains__(self, key): + return key in self.processes + + def reload(self): + """ + This operation will throw away the current dictionary contents, if any, and + read all the pid files from /proc/, instantiating a 'process' instance for + each of them. + + This is a high overhead operation, and should be avoided if the perf python + binding can be used to detect when new threads appear and existing ones + terminate. + + In RHEL it is found in the python-perf rpm package. + + More information about the perf facilities can be found in the 'perf_event_open' + man page. + """ + del self.processes + self.processes = {} + pids = os.listdir(self.basedir) + for spid in pids: + try: + pid = int(spid) + except: + continue + + self.processes[pid] = process(pid, self.basedir) + + def reload_threads(self): + for pid in self.processes.keys(): + try: + self.processes[pid].load_threads() + except OSError: + # process vanished, remove it + del self.processes[pid] + + def find_by_name(self, name): + name = name[:15] + pids = [] + for pid in self.processes.keys(): + try: + if name == self.processes[pid]["stat"]["comm"]: + pids.append(pid) + except IOError: + # We're doing lazy loading of /proc files + # So if we get this exception is because the + # process vanished, remove it + del self.processes[pid] + + return pids + + def find_by_regex(self, regex): + pids = [] + for pid in self.processes.keys(): + try: + if regex.match(self.processes[pid]["stat"]["comm"]): + pids.append(pid) + except IOError: + # We're doing lazy loading of /proc files + # So if we get this exception is because the + # process vanished, remove it + del self.processes[pid] + return pids + + def find_by_cmdline_regex(self, regex): + pids = [] + for pid in self.processes.keys(): + try: + if regex.match(process_cmdline(self.processes[pid])): + pids.append(pid) + except IOError: + # We're doing lazy loading of /proc files + # So if we get this exception is because the + # process vanished, remove it + del self.processes[pid] + return pids + + def get_per_cpu_rtprios(self, basename): + cpu = 0 + priorities="" + processed_pids = [] + while True: + name = "%s/%d" % (basename, cpu) + pids = self.find_by_name(name) + if not pids or len([n for n in pids if n not in processed_pids]) == 0: + break + for pid in pids: + try: + priorities += "%s," % self.processes[pid]["stat"]["rt_priority"] + except IOError: + # We're doing lazy loading of /proc files + # So if we get this exception is because the + # process vanished, remove it + del self.processes[pid] + processed_pids += pids + cpu += 1 + + priorities = priorities.strip(',') + return priorities + + def get_rtprios(self, name): + cpu = 0 + priorities="" + processed_pids = [] + while True: + pids = self.find_by_name(name) + if not pids or len([n for n in pids if n not in processed_pids]) == 0: + break + for pid in pids: + try: + priorities += "%s," % self.processes[pid]["stat"]["rt_priority"] + except IOError: + # We're doing lazy loading of /proc files + # So if we get this exception is because the + # process vanished, remove it + del self.processes[pid] + processed_pids += pids + cpu += 1 + + priorities = priorities.strip(',') + return priorities + + def is_bound_to_cpu(self, pid): + """ + Checks if a given pid can't have its SMP affinity mask changed. + """ + return self.processes[pid]["stat"].is_bound_to_cpu() + +class interrupts: + """ + Information about IRQs in the system. A dictionary keyed by IRQ number + will have as its value another dictionary with "cpu", "type" and "users" + keys, with the SMP affinity mask, type of IRQ and the drivers associated + with each interrupt. + + The information comes from the /proc/interrupts file, documented in + 'man procfs(5)', for instance, the 'cpu' dict is an array with one entry + per CPU present in the sistem, each value being the number of interrupts + that took place per CPU. + + E.g.: + + >>> import procfs + >>> interrupts = procfs.interrupts() + >>> thunderbolt_irq = interrupts.find_by_user("thunderbolt") + >>> print thunderbolt_irq + 34 + >>> thunderbolt = interrupts[thunderbolt_irq] + >>> print thunderbolt + {'affinity': [0, 1, 2, 3], 'type': 'PCI-MSI', 'cpu': [3495, 0, 81, 0], 'users': ['thunderbolt']} + >>> + """ + def __init__(self): + self.interrupts = {} + self.reload() + + def __getitem__(self, key): + return self.interrupts[str(key)] + + def keys(self): + return list(self.interrupts.keys()) + + def values(self): + return list(self.interrupts.values()) + + def has_key(self, key): + return str(key) in self.interrupts + + def items(self): + return self.interrupts + + def __contains__(self, key): + return str(key) in self.interrupts + + def reload(self): + del self.interrupts + self.interrupts = {} + f = open("/proc/interrupts") + + for line in f.readlines(): + line = line.strip() + fields = line.split() + if fields[0][:3] == "CPU": + self.nr_cpus = len(fields) + continue + irq = fields[0].strip(":") + self.interrupts[irq] = {} + self.interrupts[irq] = self.parse_entry(fields[1:], line) + try: + nirq = int(irq) + except: + continue + self.interrupts[irq]["affinity"] = self.parse_affinity(nirq) + + f.close() + + def parse_entry(self, fields, line): + dict = {} + dict["cpu"] = [] + dict["cpu"].append(int(fields[0])) + nr_fields = len(fields) + if nr_fields >= self.nr_cpus: + dict["cpu"] += [int(i) for i in fields[1:self.nr_cpus]] + if nr_fields > self.nr_cpus: + dict["type"] = fields[self.nr_cpus] + # look if there are users (interrupts 3 and 4 haven't) + if nr_fields > self.nr_cpus + 1: + dict["users"] = [a.strip() for a in fields[nr_fields - 1].split(',')] + else: + dict["users"] = [] + return dict + + def parse_affinity(self, irq): + try: + f = open("/proc/irq/%s/smp_affinity" % irq) + line = f.readline() + f.close() + return bitmasklist(line, self.nr_cpus) + except IOError: + return [ 0, ] + + def find_by_user(self, user): + """ + Looks up a interrupt number by the name of one of its users" + + E.g.: + + >>> import procfs + >>> interrupts = procfs.interrupts() + >>> thunderbolt_irq = interrupts.find_by_user("thunderbolt") + >>> print thunderbolt_irq + 34 + >>> thunderbolt = interrupts[thunderbolt_irq] + >>> print thunderbolt + {'affinity': [0, 1, 2, 3], 'type': 'PCI-MSI', 'cpu': [3495, 0, 81, 0], 'users': ['thunderbolt']} + >>> + """ + for i in self.interrupts.keys(): + if "users" in self.interrupts[i] and \ + user in self.interrupts[i]["users"]: + return i + return None + + def find_by_user_regex(self, regex): + """ + Looks up a interrupt number by a regex that matches names of its users" + + E.g.: + + >>> import procfs + >>> import re + >>> interrupts = procfs.interrupts() + >>> usb_controllers = interrupts.find_by_user_regex(re.compile(".*hcd")) + >>> print usb_controllers + ['22', '23', '31'] + >>> print [ interrupts[irq]["users"] for irq in usb_controllers ] + [['ehci_hcd:usb4'], ['ehci_hcd:usb3'], ['xhci_hcd']] + >>> + """ + irqs = [] + for i in self.interrupts.keys(): + if "users" not in self.interrupts[i]: + continue + for user in self.interrupts[i]["users"]: + if regex.match(user): + irqs.append(i) + break + return irqs + +class cmdline: + """ + Parses the kernel command line (/proc/cmdline), turning it into a dictionary." + + Useful to figure out if some kernel boolean knob has been turned on, as well + as to find the value associated to other kernel knobs. + + It can also be used to find out about parameters passed to the init process, + such as 'BOOT_IMAGE', etc. + + E.g.: + >>> import procfs + >>> kcmd = procfs.cmdline() + >>> print kcmd.keys() + ['LANG', 'BOOT_IMAGE', 'quiet', 'rhgb', 'rd.lvm.lv', 'ro', 'root'] + >>> print kcmd["BOOT_IMAGE"] + /vmlinuz-4.3.0-rc1+ + >>> + """ + + def __init__(self): + self.options = {} + self.parse() + + def parse(self): + f = open("/proc/cmdline") + for option in f.readline().strip().split(): + fields = option.split("=") + if len(fields) == 1: + self.options[fields[0]] = True + else: + self.options[fields[0]] = fields[1] + + f.close() + + def __getitem__(self, key): + return self.options[key] + + def keys(self): + return list(self.options.keys()) + + def values(self): + return list(self.options.values()) + + def items(self): + return self.options + +class cpuinfo: + """ + Dictionary with information about CPUs in the system. + + Please refer to 'man procfs(5)' for further information about the + '/proc/cpuinfo' file, that is the source of the information provided by this + class. The 'man lscpu(1)' also has information about a program that uses + the '/proc/cpuinfo' file. + + Using this class one can obtain the number of CPUs in a system: + + >>> cpus = procfs.cpuinfo() + >>> print cpus.nr_cpus + 4 + + It is also possible to figure out aspects of the CPU topology, such as + how many CPU physical sockets exists, i.e. groups of CPUs sharing components + such as CPU memory caches: + + >>> print len(cpus.sockets) + 1 + + Additionally dictionary with information common to all CPUs in the system is + available: + + >>> print cpus["model name"] + Intel(R) Core(TM) i7-3667U CPU @ 2.00GHz + >>> print cpus["cache size"] + 4096 KB + >>> + """ + def __init__(self, filename="/proc/cpuinfo"): + self.tags = {} + self.nr_cpus = 0 + self.sockets = [] + self.parse(filename) + + def __getitem__(self, key): + return self.tags[key.lower()] + + def keys(self): + return list(self.tags.keys()) + + def values(self): + return list(self.tags.values()) + + def items(self): + return self.tags + + def parse(self, filename): + f = open(filename) + for line in f.readlines(): + line = line.strip() + if len(line) == 0: + continue + fields = line.split(":") + tagname = fields[0].strip().lower() + if tagname == "processor": + self.nr_cpus += 1 + continue + elif tagname == "core id": + continue + self.tags[tagname] = fields[1].strip() + if tagname == "physical id": + socket_id = self.tags[tagname] + if socket_id not in self.sockets: + self.sockets.append(socket_id) + + f.close() + self.nr_sockets = self.sockets and len(self.sockets) or \ + (self.nr_cpus / ("siblings" in self.tags and int(self.tags["siblings"]) or 1)) + self.nr_cores = ("cpu cores" in self.tags and int(self.tags["cpu cores"]) or 1) * self.nr_sockets + +class smaps_lib: + """ + Representation of an mmap in place for a process. Can be used to figure + out which processes have an library mapped, etc. + + The 'perm' member can be used to figure out executable mmaps, i.e. libraries. + + The 'vm_start' and 'vm_end' in turn can be used when trying to resolve + processor instruction pointer addresses to a symbol name in a library. + """ + def __init__(self, lines): + fields = lines[0].split() + self.vm_start, self.vm_end = [int(a, 16) for a in fields[0].split("-")] + self.perms = fields[1] + self.offset = int(fields[2], 16) + self.major, self.minor = fields[3].split(":") + self.inode = int(fields[4]) + if len(fields) > 5: + self.name = fields[5] + else: + self.name = None + self.tags = {} + for line in lines[1:]: + fields = line.split() + tag = fields[0][:-1].lower() + try: + self.tags[tag] = int(fields[1]) + except: + # VmFlags are strings + self.tags[tag] = fields + + def __getitem__(self, key): + return self.tags[key.lower()] + + def keys(self): + return list(self.tags.keys()) + + def values(self): + return list(self.tags.values()) + + def items(self): + return self.tags + + +class smaps: + """ + List of libraries mapped by a process. Parses the lines in + the /proc/PID/smaps file, that is further documented in the + procfs(5) man page. + + Example: Listing the executable maps for the 'sshd' process: + + >>> import procfs + >>> processes = procfs.pidstats() + >>> sshd = processes.find_by_name("sshd") + >>> sshd_maps = procfs.smaps(sshd[0]) + >>> for i in range(len(sshd_maps)): + ... if 'x' in sshd_maps[i].perms: + ... print "%s: %s" % (sshd_maps[i].name, sshd_maps[i].perms) + ... + /usr/sbin/sshd: r-xp + /usr/lib64/libnss_files-2.20.so: r-xp + /usr/lib64/librt-2.20.so: r-xp + /usr/lib64/libkeyutils.so.1.5: r-xp + /usr/lib64/libkrb5support.so.0.1: r-xp + /usr/lib64/libfreebl3.so: r-xp + /usr/lib64/libpthread-2.20.so: r-xp + ... + """ + def __init__(self, pid): + self.pid = pid + self.entries = [] + self.reload() + + def parse_entry(self, f, line): + lines = [] + if not line: + line = f.readline().strip() + if not line: + return + lines.append(line) + while True: + line = f.readline() + if not line: + break + line = line.strip() + if line.split()[0][-1] == ':': + lines.append(line) + else: + break + self.entries.append(smaps_lib(lines)) + return line + + def __len__(self): + return len(self.entries) + + def __getitem__(self, index): + return self.entries[index] + + def reload(self): + f = open("/proc/%d/smaps" % self.pid) + line = None + while True: + line = self.parse_entry(f, line) + if not line: + break + f.close() + self.nr_entries = len(self.entries) + + def find_by_name_fragment(self, fragment): + result = [] + for i in range(self.nr_entries): + if self.entries[i].name and \ + self.entries[i].name.find(fragment) >= 0: + result.append(self.entries[i]) + + return result + +class cpustat: + """ + CPU statistics, obtained from a line in the '/proc/stat' file, Please + refer to 'man procfs(5)' for further information about the '/proc/stat' + file, that is the source of the information provided by this class. + """ + + def __init__(self, fields): + self.name = fields[0] + (self.user, + self.nice, + self.system, + self.idle, + self.iowait, + self.irq, + self.softirq) = [int(i) for i in fields[1:8]] + if len(fields) > 7: + self.steal = int(fields[7]) + if len(fields) > 8: + self.guest = int(fields[8]) + + def __repr__(self): + s = "< user: %s, nice: %s, system: %s, idle: %s, iowait: %s, irq: %s, softirq: %s" % \ + (self.user, self.nice, self.system, self.idle, self.iowait, self.irq, self.softirq) + if hasattr(self, 'steal'): + s += ", steal: %d" % self.steal + if hasattr(self, 'guest'): + s += ", guest: %d" % self.guest + return s + ">" + +class cpusstats: + """ + Dictionary with information about CPUs in the system. First entry in the + dictionary gives an aggregate view of all CPUs, each other entry is about + separate CPUs. Please refer to 'man procfs(5)' for further information + about the '/proc/stat' file, that is the source of the information provided + by this class. + """ + def __init__(self, filename = "/proc/stat"): + self.entries = {} + self.time = None + self.hertz = os.sysconf(2) + self.filename = filename + self.reload() + + def __iter__(self): + return iter(self.entries) + + def __getitem__(self, key): + return self.entries[key] + + def __len__(self): + return len(list(self.entries.keys())) + + def keys(self): + return list(self.entries.keys()) + + def values(self): + return list(self.entries.values()) + + def items(self): + return self.entries + + def reload(self): + last_entries = self.entries + self.entries = {} + f = open(self.filename) + for line in f.readlines(): + fields = line.strip().split() + if fields[0][:3].lower() != "cpu": + continue + c = cpustat(fields) + if c.name == "cpu": + idx = 0 + else: + idx = int(c.name[3:]) + 1 + self.entries[idx] = c + f.close() + last_time = self.time + self.time = time.time() + if last_entries: + delta_sec = self.time - last_time + interval_hz = delta_sec * self.hertz + for cpu in self.entries.keys(): + if cpu not in last_entries: + curr.usage = 0 + continue + curr = self.entries[cpu] + prev = last_entries[cpu] + delta = (curr.user - prev.user) + \ + (curr.nice - prev.nice) + \ + (curr.system - prev.system) + curr.usage = (delta / interval_hz) * 100 + if curr.usage > 100: + curr.usage = 100 + +if __name__ == '__main__': + import sys + + ints = interrupts() + + for i in ints.interrupts.keys(): + print("%s: %s" % (i, ints.interrupts[i])) + + options = cmdline() + for o in options.options.keys(): + print("%s: %s" % (o, options.options[o])) + + cpu = cpuinfo() + print("\ncpuinfo data: %d processors" % cpu.nr_cpus) + for tag in cpu.keys(): + print("%s=%s" % (tag, cpu[tag])) + + print("smaps:\n" + ("-" * 40)) + s = smaps(int(sys.argv[1])) + for i in range(s.nr_entries): + print("%#x %s" % (s.entries[i].vm_start, s.entries[i].name)) + print("-" * 40) + for a in s.find_by_name_fragment(sys.argv[2]): + print(a["Size"]) + + ps = pidstats() + print(ps[1]) + + cs = cpusstats() + while True: + time.sleep(1) + cs.reload() + for cpu in cs: + print("%s: %d" % (cpu.name, cpu.usage)) + print("-" * 10) diff --git a/procfs/sysctl.py b/procfs/sysctl.py new file mode 100755 index 0000000..8b256ab --- /dev/null +++ b/procfs/sysctl.py @@ -0,0 +1,68 @@ +#! /usr/bin/python +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +class sysctl: + def __init__(self): + self.cache = {} + + def __getitem__(self, key): + if key not in self.cache: + value = self.read(key) + if value == None: + return None + self.cache[key] = value + + return self.cache[key] + + def __setitem__(self, key, value): + oldvalue = self[key] + + if oldvalue == None: + raise IOError + elif oldvalue != value: + self.write(key, value) + self.cache[key] = value + + def keys(self): + return list(self.cache.keys()) + + def read(self, key): + try: + f = open("/proc/sys/%s" % key.replace(".", "/")) + except: + return None + value = f.readline().strip() + f.close() + return value + + def write(self, key, value): + try: + f = open("/proc/sys/%s" % key.replace(".", "/"), "w") + except: + return + f.write(value) + f.close() + + def refresh(self): + for key in self.cache(): + del self.cache[key] + value = self.read(key) + if value != None: + self.cache[key] = value diff --git a/procfs/utilist.py b/procfs/utilist.py new file mode 100755 index 0000000..13f40fa --- /dev/null +++ b/procfs/utilist.py @@ -0,0 +1,53 @@ +#! /usr/bin/python +# -*- python -*- +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +from six.moves import range + + +def hexbitmask(l, nr_entries): + hexbitmask = [] + bit = 0 + mask = 0 + for entry in range(nr_entries): + if entry in l: + mask |= (1 << bit) + bit += 1 + if bit == 32: + bit = 0 + hexbitmask.insert(0, mask) + mask = 0 + + if bit < 32 and mask != 0: + hexbitmask.insert(0, mask) + + return hexbitmask + +def bitmasklist(line, nr_entries): + hexmask = line.strip().replace(",", "") + bitmasklist = [] + entry = 0 + bitmask = bin(int(hexmask, 16))[2::] + for i in reversed(bitmask): + if int(i) & 1: + bitmasklist.append(entry) + entry +=1 + if entry == nr_entries: + break + return bitmasklist diff --git a/rpm/SPECS/python-linux-procfs.spec b/rpm/SPECS/python-linux-procfs.spec new file mode 100644 index 0000000..0228c79 --- /dev/null +++ b/rpm/SPECS/python-linux-procfs.spec @@ -0,0 +1,147 @@ +%if 0%{?fedora} +%global with_python3 1 +%else +%global without_python3 1 +%endif + +Name: python-linux-procfs +Version: 0.6 +Release: 1%{?dist} +License: GPLv2 +Summary: Linux /proc abstraction classes +Group: System Environment/Libraries +Source: https://cdn.kernel.org/pub/software/libs/python/%{name}/%{name}-%{version}.tar.xz +URL: https://rt.wiki.kernel.org/index.php/Tuna +BuildArch: noarch +BuildRequires: python2-devel +BuildRequires: python-setuptools +%if 0%{?with_python3} +BuildRequires: python3-devel +BuildRequires: python3-setuptools +%endif +BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) + +%global _description\ +Abstractions to extract information from the Linux kernel /proc files. + +%description %_description + +%package -n python2-linux-procfs +Summary: %summary +%{?python_provide:%python_provide python2-linux-procfs} + +Requires: python-six + +%description -n python2-linux-procfs %_description + +%if 0%{?with_python3} +%package -n python3-linux-procfs +Summary: %summary +%{?python_provide:%python_provide python3-linux-procfs} + +Requires: python3-six + +%description -n python3-linux-procfs %_description +%endif + +%prep +%autosetup -p1 + +%build +%py2_build +%if 0%{?with_python3} +%py3_build +%endif + +%install +rm -rf %{buildroot} +%py2_install +%if 0%{?with_python3} +%py3_install +%endif + +%clean +rm -rf %{buildroot} + +%files -n python2-linux-procfs +%defattr(0755,root,root,0755) +%{python2_sitelib}/procfs/ +%if 0%{?without_python3} +%{_bindir}/pflags +%endif +%defattr(0644,root,root,0755) +%{python2_sitelib}/python_linux_procfs*.egg-info +%license COPYING + +%if 0%{?with_python3} +%files -n python3-linux-procfs +%defattr(0755,root,root,0755) +%{_bindir}/pflags +%{python3_sitelib}/procfs/ +%defattr(0644,root,root,0755) +%{python3_sitelib}/python_linux_procfs*.egg-info +%license COPYING +%endif + +%changelog +* Thu Aug 9 2018 Jiri Kastner - 0.6-1 +- moved cannot)set*affinity calls from tuna + +* Tue Nov 21 2017 Jiri Kastner - 0.5.1-1 +- missed snippet in specfile for python2 only +- added scripts to setup.py, pflags renamed and added to setup.py + +* Mon Nov 20 2017 Jiri Kastner - 0.5-1 +- added python3 support + +* Tue Sep 26 2017 Jiri Kastner - 0.4.11-1 +- fixed rpmlint compliants (url, source) + +* Thu Dec 22 2016 Jiri Kastner - 0.4.10-1 +- fixed affinity parsing with cpu numbers greater than 31 +- added test for fix above + +* Thu Oct 8 2015 Arnaldo Carvalho de Melo - 0.4.9-1 +- Adds documentations to classes, more work to do on methods +- Fixes parsing of users in /proc/interrupts users field +- Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1245677 + +* Tue Jun 23 2015 Arnaldo Carvalho de Melo - 0.4.8-1 +- Support spaces in COMM names +- Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1232394 + +* Thu Jun 11 2015 Arnaldo Carvalho de Melo - 0.4.7-1 +- Fix pidstat.process_flag() +- Introduce pflags utility +- Parse IRQ affinities for !root +- Add PF_NO_SETAFFINITY const + +* Wed Jun 5 2013 Jiri Kastner - 0.4.6-1 +- support for parsing cgroups +- support for parsing environ variables + +* Mon May 10 2010 Arnaldo Carvalho de Melo - 0.4.5-1 +- Fix https://bugzilla.redhat.com/show_bug.cgi?id=577365 + +* Mon Feb 10 2009 Arnaldo Carvalho de Melo - 0.4.4-1 +- Even more fixes due to the fedora review process + +* Mon Feb 9 2009 Arnaldo Carvalho de Melo - 0.4.3-1 +- Fixups due to the fedora review process + +* Tue Aug 12 2008 Arnaldo Carvalho de Melo - 0.4.2-1 +- interrupts: Add find_by_user_regex +- process: Always set the "cmdline" array, even if empty +- pidstats: Remove dead processes in find_by_name() +- pidstats: Add process class to catch dict references for late parsing +- pidstats: Move the /proc/PID/{stat,status} parsing to classes +- pidstats: Introduce process_flags method + +* Tue Aug 12 2008 Arnaldo Carvalho de Melo - 0.4-1 +- Per process flags needed by tuna + +* Fri Jun 13 2008 Arnaldo Carvalho de Melo - 0.3-1 +- Support CPU hotplug + +* Mon Feb 25 2008 Arnaldo Carvalho de Melo - 0.1-1 +- package created diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..036a1fa --- /dev/null +++ b/setup.py @@ -0,0 +1,28 @@ +#!/usr/bin/python +from distutils.sysconfig import get_python_lib +from setuptools import setup +from os.path import isfile, join +import glob +import os + +if isfile("MANIFEST"): + os.unlink("MANIFEST") + +# Get PYTHONLIB with no prefix so --prefix installs work. +PYTHONLIB = join(get_python_lib(standard_lib=1, prefix=''), 'site-packages') + +setup(name="python-linux-procfs", + version = "0.6", + description = "Linux /proc abstraction classes", + author = "Arnaldo Carvalho de Melo", + author_email = "acme@redhat.com", + url = "http://userweb.kernel.org/python-linux-procfs", + license = "GPLv2", + long_description = +"""\ +Abstractions to extract information from the Linux kernel /proc files. +""", + packages = ["procfs"], + scripts = ['pflags'], + install_requires = ['six'], + )