From 77e8740d6720ff4faca630e271c270e40d90accd Mon Sep 17 00:00:00 2001 From: Packit Service Date: Mar 28 2021 23:20:37 +0000 Subject: iotop-0.6 base --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..864fa4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +/build/ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..b7b5f53 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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 + + 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) + + 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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) year 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/ChangeLog b/ChangeLog new file mode 100644 index 0000000..067a99e --- /dev/null +++ b/ChangeLog @@ -0,0 +1,897 @@ +2013-05-27 Guillaume Chazarain + + * NEWS: Document signature + +2013-05-27 Guillaume Chazarain + + * release.sh: GPG sign all released files + +2013-05-26 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump. + +2013-05-26 Guillaume Chazarain + + * iotop/ui.py: Clean exit also on SIGTERM otherwise the terminal is + unusable. + +2013-05-26 Guillaume Chazarain + + * iotop/ui.py: Python3 can print UTF-8 to curses, python2 can't so + let's handle both. + +2013-05-26 Guillaume Chazarain + + * iotop/ui.py: Cleanly exit on SIGINT otherwise python3 will leave + the terminal in an unusable state. + +2013-05-26 Guillaume Chazarain + + * .install-rpm.sh: No need to make the RPM install script move bin/ + to sbin/ now that setup.py installs to sbin/. + +2013-05-26 Guillaume Chazarain + + * setup.py: Make setup.py install the iotop script in sbin/ instead + of bin/. + +2013-05-26 Guillaume Chazarain + + * iotop/ui.py: In some setup closing the xterm window only has the + effect of deleting the pty. Then iotop would be busy looping + reading on stdin. Instead we should detect the terminal deletion and + exit. When this happens iotop receives (0, 25) as an event, which + is (stdin, select.POLLIN|select.POLLERR|select.POLLHUP). Also, represent an empty even list as [] instead of 0, this is just + cosmetic. + +2013-02-04 Paul Wise + + * iotop/data.py, iotop/ioprio.py, iotop/ui.py, iotop/vmstat.py: Fix + the FSF address embedded in a few files + +2013-02-04 Paul Wise + + * sbin/iotop: Fix python3 compatibility for iotop when installed + +2013-02-03 Guillaume Chazarain + + * README, iotop/data.py: Update python requirements + +2013-02-03 Guillaume Chazarain + + * NEWS: Also advertise the move to sbin/ as it's significant + +2013-02-03 Guillaume Chazarain + + * .install-rpm.sh: The RPM should also install to sbin. + +2013-02-03 Guillaume Chazarain + + * bin/iotop, sbin/iotop: Moved to sbin. + +2013-02-03 Guillaume Chazarain + + * .install-rpm.sh, MANIFEST.in: Finish man page renaming + +2013-02-03 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump + +2013-02-03 Guillaume Chazarain + + * NEWS, THANKS: Advertise the newly introduced differentiation + between total and actual I/O. + +2013-02-03 Guillaume Chazarain + + * iotop/ui.py: 80 cols + +2012-10-10 Igor Bazhitov + + * iotop.8, iotop/data.py, iotop/ui.py: Add 'Actual' bandwidth stats + to summary header 'Total' values in the summary header may look confusing to users. + They represent actual kernel <-> disk I/O bandwidth, while + individual values for processes/threads show process <-> kernel I/O + bandwidth. Rename 'Total' to 'Actual' and add old 'Total' status line that sums + up all individual process/thread bandwidths. Explain the difference + between 'Total' and 'Actual' in the manpage. + +2012-10-09 Igor Bazhitov + + * README: Update manpage name in README + +2012-12-05 Paul Wise + + * iotop/data.py: Fix crash when running under python3. This reverts cd6ffb5913664844290f44a7ea48533caf8c459e Traceback (most recent call last): File "./iotop.py", line 12, in main() File "./iotop/iotop/ui.py", line 597, in main main_loop() File "./iotop/iotop/ui.py", line 587, in main_loop = lambda: run_iotop(options) File "./iotop/iotop/ui.py", line 485, in run_iotop return curses.wrapper(run_iotop_window, options) File "/usr/lib/python3.2/curses/wrapper.py", line 43, in wrapper return func(stdscr, *args, **kwds) File "./iotop/iotop/ui.py", line 478, in run_iotop_window ui.run() File "./iotop/iotop/ui.py", line 153, in run total = self.process_list.refresh_processes() File "./iotop/iotop/data.py", line 459, in refresh_processes self.processes.items() if File "./iotop/iotop/data.py", line 460, in process.update_stats()]) File "./iotop/iotop/data.py", line 358, in update_stats for tid, thread in self.threads.items(): RuntimeError: + dictionary changed size during iteration [This is valid since 0fc4ab84c8cbba1fbe83dc71fb89100b87c54898 added the self.threads = dict(...)] + +2012-09-02 Paul Wise + + * iotop.1, iotop.8, setup.py: Move iotop out of the path for users + +2012-09-03 Guillaume Chazarain + + * iotop/ui.py: Fix the setting of the I/O priority and advertise it + a litle more. + +2012-09-03 Guillaume Chazarain + + * iotop/ui.py: Here we print a string, not bytes. + +2012-09-03 Guillaume Chazarain + + * iotop/netlink.py: Remove stray print added during the python3 + conversion. + +2012-09-03 Guillaume Chazarain + + * iotop/data.py: Restore compatibility with python2 + +2012-09-03 Guillaume Chazarain + + * iotop/data.py: Put back code deleted in the python3 conversion + +2012-09-03 Guillaume Chazarain + + * iotop/data.py: Some missed python3 conversions + +2012-09-03 Guillaume Chazarain + + * iotop/data.py, iotop/ui.py: Cosmetic fixes + +2012-09-02 Paul Wise + + * iotop/data.py, iotop/genetlink.py, iotop/ioprio.py, + iotop/netlink.py, iotop/ui.py: Port to Python 3 Not entirely sure about all parts of this but it works in Python 2/3 + +2012-09-03 Guillaume Chazarain + + * THANKS, iotop/data.py: Show custom thread names. + +2012-05-13 Paul Wise + + * iotop/ui.py: Improve the message that is printed when Linux denies + access to taskstats. + +2012-03-08 Guillaume Chazarain + + * README: Consistent option names + +2012-01-22 Guillaume Chazarain + + * iotop/ui.py: Restore the default SIGPIPE handler so that sudo + ./iotop.py -b|head does what's expected. + +2012-01-22 Guillaume Chazarain + + * NEWS, iotop/ui.py: Adapt the display to the maximum pid width + +2012-01-18 Guillaume Chazarain + + * .gitignore: Ignore the build directory. + +2011-10-30 Guillaume Chazarain + + * .install-rpm.sh, setup.cfg: Actually install-rpm.sh is still + needed. + +2011-10-30 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump + +2011-10-15 Guillaume Chazarain + + * iotop/ui.py: Explain that iotop now requires root. + https://lkml.org/lkml/2011/10/1/170 + + http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=1a51410abe7d0ee4b1d112780f46df87d3621043 + +2011-09-17 Thomas Guettler + + * iotop/ui.py: Right-justify the header so that numbers stop + "bouncing". + +2011-08-04 Guillaume Chazarain + + * iotop/ui.py: When printing the time, print it also in the summary + +2011-04-10 Guillaume Chazarain + + * iotop/data.py, iotop/genetlink.py, iotop/ioprio.py, + iotop/netlink.py, iotop/ui.py, iotop/vmstat.py: Address some + pyflakes and pychecker warnings + +2011-03-28 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump + +2011-03-14 Guillaume Chazarain + + * iotop/ui.py: Show stats since iotop started, not since 'a' was + pressed. This is to avoid losing valuable data when 'a' is + inadvertently pressed. + +2011-03-13 Guillaume Chazarain + + * iotop/ui.py: Force UTF-8 output even if the locale is not set to + UTF-8. At worst it will output garbage, which is better than + crashing in this case. + +2011-01-16 Guillaume Chazarain + + * THANKS, iotop/data.py, iotop/genetlink.py: Fix netlink message + parsing to accept alignement padding. https://lkml.org/lkml/2010/12/13/176 + https://lkml.org/lkml/2010/12/29/237 + +2010-12-15 Guillaume Chazarain + + * iotop/data.py: Removing dead code + +2010-12-14 Guillaume Chazarain + + * NEWS: Grammar + +2010-12-14 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump + +2010-12-14 Guillaume Chazarain + + * MANIFEST.in, install-rpm.sh, setup.cfg: Try to do without + install-rpm.sh + +2010-12-14 Guillaume Chazarain + + * iotop/ui.py: With addstr instead of insstr we get a harmless + exception when writing on the last column. Confirmed by http://ubuntuforums.org/showthread.php?t=457689 + addstr() raises an exception but the string is printed anyway + +2010-12-14 Guillaume Chazarain + + * iotop/ui.py: Back to addstr because of: + http://marc.info/?l=ncurses-bug&m=125233342917443&w=3 insstr (from ncursesw) UTF-8 issue => The cursor position was not + updated for wide characters by insstr() + +2010-09-06 Guillaume Chazarain + + * bin/iotop: Revert "Some distributions have a default distutils + prefix which is not in sys.path, so installed modules cannot be + imported." This reverts commit d0812c2024a1f8edb081a2996af12efacdf8c961. Actually I don't think this is needed for now. + +2010-09-06 Guillaume Chazarain + + * bin/iotop: Some distributions have a default distutils prefix + which is not in sys.path, so installed modules cannot be imported. + To address that, explicitely add the path where the module is + supposed to be installed to sys.path. + +2010-09-04 Guillaume Chazarain + + * THANKS, iotop/ioprio.py: Support for getting and setting IO + priority on armel and hppa architectures. + +2010-09-04 Guillaume Chazarain + + * THANKS, iotop/data.py: Instead of assuming the pid field is 4 + bytes long, take its length from the header. This is needed for + http://lkml.org/lkml/2010/2/12/167 [PATCH] delayacct: align to 8 + byte boundary on 64-bit systems + +2010-08-22 Paul Wise + + * iotop/ui.py: Fix traceback with an invalid locale. Closes: http://bugs.debian.org/593846 + +2010-06-27 Guillaume Chazarain + + * NEWS, iotop/version.py: Bump version + +2010-06-26 Guillaume Chazarain + + * MANIFEST.in: We no longer use setuptools. + +2010-06-26 Guillaume Chazarain + + * iotop.1: Document the competition + +2010-06-26 Guillaume Chazarain + + * iotop/data.py: Whitespace fixes + +2010-05-31 Paul Wise + + * README, iotop.1, iotop/data.py: Document the requirement for + CONFIG_VM_EVENT_COUNTERS and check for it on startup. Closes: http://bugs.debian.org/574346 + +2010-03-17 Paul Wise + + * iotop/data.py: Do not report requirements that are available. Closes: http://bugs.debian.org/574246 + +2010-06-26 Guillaume Chazarain + + * release.sh: Build the source distribution using ./setup.py sdist + +2010-06-26 Guillaume Chazarain + + * MANIFEST.in: Make sure to bundle all files in the source + distribution + +2010-06-26 Guillaume Chazarain + + * iotop.py, iotop/data.py, iotop/ioprio.py, iotop/ui.py, + iotop/vmstat.py: Added GPLv2+ headers + +2010-04-27 Guillaume Chazarain + + * iotop/genetlink.py, iotop/netlink.py: Johannes relicensed + pynl80211 to GPL version 2 or later. + +2010-04-26 Jiri Olsa + + * iotop/netlink.py: This broke on ppc64. Let's make U32Attr + consistent with u32(). + +2010-01-11 Guillaume Chazarain + + * iotop/version.py: Bump the version number + +2010-01-11 Guillaume Chazarain + + * release.sh, setup.cfg: Bring back the building of RPMs and + integration of the ChangeLog + +2010-01-11 Guillaume Chazarain + + * NEWS, install-rpm.sh, setup.cfg, setup.py: Stopped using + setuptools in favor of straight distutils + +2010-01-02 Guillaume Chazarain + + * iotop/ui.py: Negative sizes shouldn't ever happen, but let's + handle them gracefully anyway. + +2009-12-13 Guillaume Chazarain + + * : commit b76e492ce5dbdecb198109e1bbc77aad909a0a67 Author: + Guillaume Chazarain Date: Sun Dec 13 21:17:56 + 2009 +0100 + +2009-12-13 Guillaume Chazarain + + * README, iotop/data.py: Document the new python requirements + +2009-12-13 Jiri Olsa + + * NEWS, THANKS, iotop/data.py, iotop/netlink.py: Compatibility with + python2.4 using the ctypes module + +2009-12-13 Guillaume Chazarain + + * iotop/genetlink.py: 80 columns + +2009-12-13 Guillaume Chazarain + + * iotop/genetlink.py, iotop/netlink.py: Untabify + +2009-12-13 Guillaume Chazarain + + * iotop/data.py: Don't use all() as it was introduced in python-2.5 + +2009-11-05 Guillaume Chazarain + + * THANKS, iotop/data.py: Fix a crash were iotop could open + /proc/PID/status but not read it as the process disappeared by then. + +2009-09-26 Guillaume Chazarain + + * iotop/ui.py: Default to 0 instead of None + +2009-09-22 Guillaume Chazarain + + * iotop/data.py: commit d4cab23b1c8c2f91ae7b353087bc60e7659620ef + broke iotop -o + +2009-09-06 Guillaume Chazarain + + * : commit b8bf63094a8903004126c8293d1874ad0565e68a Author: Paul + Wise Date: Sun Sep 6 23:19:46 2009 +0200 + +2009-09-06 Guillaume Chazarain + + * iotop/data.py: ioprio.sort_key() expects keys starting with '?' to + be at least two character long. It was not the case when different + threads in the same process had different ionice values, so adjust + the ionice key in this case. Bug reported by: Paul Wise + +2009-09-06 Guillaume Chazarain + + * iotop/ui.py: Make it even more obvious that something is wrong + when CONFIG_TASK_DELAY_ACCT is missing + +2009-09-06 Guillaume Chazarain + + * iotop/data.py: Detect python-2.5 before importing incompatible + stuff + +2009-08-30 Guillaume Chazarain + + * iotop/data.py: Turns out returning a list is faster than + iterating. + +2009-08-30 Guillaume Chazarain + + * iotop/data.py: Some more minor optimizations + +2009-08-30 Guillaume Chazarain + + * iotop/data.py: Optimize Stats.__init__ so that Stats.accumulate + can be cleaned up + +2009-08-29 Guillaume Chazarain + + * iotop/data.py: Gracefully handle disappearing PIDs + +2009-08-29 Guillaume Chazarain + + * iotop/ui.py: Faster ui.human_size() + +2009-08-29 Guillaume Chazarain + + * iotop/data.py: Faster ProcessList.list_dir() + +2009-08-29 Guillaume Chazarain + + * iotop/ioprio.py: Optimization: call getpriority() instead of + reading /proc + +2009-08-29 Guillaume Chazarain + + * iotop/data.py: Finish the implementation of the UID cache... by + actually caching the UID + +2009-08-29 Guillaume Chazarain + + * iotop/data.py, iotop/ui.py: 80 columns + +2009-08-29 Guillaume Chazarain + + * iotop/data.py: Cache the taskstats request in the thread_info as + building it every time is a hotspot + +2009-08-29 Guillaume Chazarain + + * iotop/data.py: Optimize even further the hotspot by unrolling the + loop and keeping the __dict__ objects in local variables. + +2009-08-29 Guillaume Chazarain + + * iotop/data.py: Optimize even further Stats.accumulate as it's a + hotspot: don't create a new Stats object everytime on every + invocation, keep updating the same object. + +2009-08-23 Guillaume Chazarain + + * NEWS, iotop/data.py, iotop/ui.py: Added a heuristic to detect + kernels without CONFIG_TASK_DELAY_ACCT + +2009-08-02 Guillaume Chazarain + + * iotop/data.py: Use .__dict__ instead of [gs]etattr as it's + slightly faster. + +2009-08-02 Guillaume Chazarain + + * iotop/ui.py: Use insstr instead of addstr so that we can write in + the last column, but this means we have to be careful not to add + trailing garbage as it would appear on the next line. + +2009-08-02 Guillaume Chazarain + + * NEWS, iotop/ui.py: Split long command lines in the middle instead + of cutting them at the end. + +2009-08-02 Guillaume Chazarain + + * NEWS, iotop/version.py: Bump version. + +2009-06-10 Guillaume Chazarain + + * release.sh: Stop building RPMs after my move to Ubuntu + +2009-06-10 Guillaume Chazarain + + * NEWS, iotop/ui.py: Fixed column sorting with --accumulated + +2009-06-10 Guillaume Chazarain + + * NEWS, iotop/data.py, iotop/ui.py: Fixed interaction between + --accumulated and --only + +2009-06-10 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump and start documenting new + features + +2009-05-19 Guillaume Chazarain + + * THANKS, iotop/ioprio.py: Fixed ioprio_get syscall detection on + i386 userspace/x86_64 kernel + (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=529429) + +2009-05-05 Guillaume Chazarain + + * MANIFEST.in, README, setup.cfg: Include a light README + +2009-05-05 Guillaume Chazarain + + * iotop.1: Escape even more minus signs in the iotop manual page + +2009-05-05 Paul Wise + + * iotop.1: Escape some more minus signs in the iotop manual page + +2009-05-04 Guillaume Chazarain + + * NEWS, THANKS, iotop.1, iotop/ui.py: Added --quiet + +2009-05-03 Guillaume Chazarain + + * NEWS, THANKS, iotop.1, iotop/ui.py: Added --time + +2009-05-02 Guillaume Chazarain + + * NEWS, iotop.1, iotop/data.py, iotop/ui.py: Added the -k, + --kilobytes option + +2009-05-02 Guillaume Chazarain + + * MANIFEST.in, setup.py: Upgrade setuptools from 0.6c6 to 0.6c9 + +2009-03-31 Guillaume Chazarain + + * MANIFEST.in, release.sh, setup.cfg: Include a ChangeLog in the + release + +2009-03-30 Guillaume Chazarain + + * iotop/data.py: Put kernel threads between square brackets + +2009-03-30 Guillaume Chazarain + + * iotop/ui.py: Properly sanitize the value in the error report + +2009-03-30 Guillaume Chazarain + + * NEWS, iotop.1, iotop/data.py, iotop/ui.py: - Added the --accumulated option to show the accumulated traffic + instead of the current bandwidth (dynamically toggled with 'a') - Resist to process dying during the taskstats retrieval - Adjusted column headers + +2009-03-29 Guillaume Chazarain + + * NEWS: Document some recent changes + +2009-03-29 Guillaume Chazarain + + * iotop/data.py, iotop/ui.py, iotop/vmstat.py: - Manage a two level tree of processes: o with --processes: + [tgids...] -> [tid...] o without: [tids...] -> [tid] => This handles + nicely the case where a thread dies during a sampling period and we + should drop its stats. - Don't cache the ioprio as it may change - Get the total I/O bandwidth from /proc/vmstat instead of summing + it, as we can count some of it twice (ntfs-3g, nfsd...) + +2009-03-29 Guillaume Chazarain + + * iotop/ui.py: Added --profile + +2009-03-29 Guillaume Chazarain + + * iotop/data.py: Don't crash when a thread just disappeared + +2009-03-29 Guillaume Chazarain + + * iotop/data.py, iotop/ui.py: Better UID detection: read it from + stat(/proc/PID) instead of /proc/PID/status and cache it only if not + running as root as the process may setuid(). Rewrite + check_if_valid() to is_monitored(). Also re-read /proc/PID/status + if needed when re-reading /proc/PID/cmdline. + +2009-01-31 Guillaume Chazarain + + * THANKS, iotop/ui.py: From: Ryan Lovett + When running in batch mode, iotop doesn't flush its output so if + you're writing to a file, you won't see anything (e.g. via 'tail + -f') until iotop terminates + +2008-12-29 Guillaume Chazarain + + * iotop/data.py: Add a meaningful __repr__() + +2008-12-29 Guillaume Chazarain + + * iotop/data.py: If a new pinfo() is successfully created but we + cannot get its taskstats, it will not have a .ioprio field, so it + must be garbage collected. So, initialize .mark to False so that + incompletely built objects are garbage collected. Traceback (most recent call last): File "./iotop.py", line 11, in main() File "/home/g/iotop/iotop/ui.py", line 271, in main curses.wrapper(run_iotop, options) File "/usr/lib/python2.5/curses/wrapper.py", line 44, in wrapper return func(stdscr, *args, **kwds) File "/home/g/iotop/iotop/ui.py", line 226, in run_iotop ui.run() File "/home/g/iotop/iotop/ui.py", line 97, in run self.process_list.duration) File "/home/g/iotop/iotop/ui.py", line 195, in refresh_display lines = self.get_data() File "/home/g/iotop/iotop/ui.py", line 183, in get_data return map(format, processes) File "/home/g/iotop/iotop/ui.py", line 167, in format line = '%5d %4s %-8s %11s %11s %7s %7s ' % (p.pid, p.ioprio, + AttributeError: 'pinfo' object has no attribute 'ioprio' + +2008-12-28 Guillaume Chazarain + + * iotop/data.py: The I/O priority can be dynamically changed, so we + must re-fetch it every time. + +2008-12-28 Guillaume Chazarain + + * iotop.1, iotop/ui.py: Added the 'p' key to dynamically toggle the + --processes option + +2008-12-25 Guillaume Chazarain + + * iotop/ui.py: The interactive control 'O' is the same as 'o'. + +2008-12-25 Guillaume Chazarain + + * iotop.1, iotop/data.py, iotop/ioprio.py, iotop/ui.py: Added + support for showing the I/O priority + +2008-12-23 Guillaume Chazarain + + * iotop/ui.py: More verbose error handling for this exception: + Traceback (most recent call last): File "./iotop.py", line 11, in main() File "/src/iotop/iotop/iotop/ui.py", line 249, in main curses.wrapper(run_iotop, options) File "/usr/lib64/python2.5/curses/wrapper.py", line 44, in wrapper return func(stdscr, *args, **kwds) File "/src/iotop/iotop/iotop/ui.py", line 205, in run_iotop ui.run() File "/src/iotop/iotop/iotop/ui.py", line 95, in run self.process_list.duration) File "/src/iotop/iotop/iotop/ui.py", line 198, in refresh_display self.win.addstr(i + 2, 0, lines[i].encode('utf-8')) + _curses.error: addstr() returned ERR + +2008-11-16 Guillaume Chazarain + + * iotop/ui.py: Also keep only 2 decimal digits when printing bytes + per second + +2008-09-07 Guillaume Chazarain + + * NEWS, iotop/version.py: Version bump and mention that -P is now + fully implemented + +2008-09-07 Guillaume Chazarain + + * iotop.1, iotop/ui.py: Clarify -p help text, and cosmetically add a + terminating '.'. + +2008-09-07 Guillaume Chazarain + + * iotop.1: iotop is a mix of top(1) and vmstat(1) + +2008-09-07 Guillaume Chazarain + + * iotop.1, iotop/data.py: Precisely document required kernel options + http://bugs.debian.org/497360 + +2008-09-06 Guillaume Chazarain + + * iotop/data.py: Reimplement -P without using the half implemented + TASKSTATS_CMD_ATTR_TGID + +2008-09-06 Guillaume Chazarain + + * iotop/data.py, iotop/ui.py: Cleanup: introduce a Stats class to + aggregate the useful output from taskstats insteaf of using a dict. + +2008-08-18 Guillaume Chazarain + + * iotop/data.py: It seems the Name: field can sometimes be empty. + http://bugs.debian.org/492568 + +2008-08-18 Guillaume Chazarain + + * iotop/data.py: Cosmetic + +2008-07-07 Guillaume Chazarain + + * NEWS, iotop/version.py: The new features list is not that long + +2008-06-24 Guillaume Chazarain + + * iotop/data.py: Also handle invalid UTF-8 + +2008-06-23 Guillaume Chazarain + + * iotop/ui.py: Unlike insstr, addstr is picky about lines wider than + the terminal. + +2008-06-23 Guillaume Chazarain + + * iotop/data.py, iotop/ui.py: Try harder at handling UTF-8 + +2008-06-18 Guillaume Chazarain + + * NEWS, THANKS, iotop/data.py, iotop/ui.py: UTF-8 strings are now + correctly handled. + +2008-06-18 Guillaume Chazarain + + * NEWS, iotop/version.py: Bump version + +2008-05-28 Guillaume Chazarain + + * iotop.1: Fix for + + http://lintian.debian.org/reports/tags/hyphen-used-as-minus-sign.html + +2008-05-23 Guillaume Chazarain + + * MANIFEST.in, install-rpm.sh, setup.cfg, setup.py: Package the man + page + +2008-05-22 Guillaume Chazarain + + * iotop/ui.py: Reordered the option like in the man page, as it's a + more sensible ordering + +2008-05-22 Guillaume Chazarain + + * THANKS, iotop.1: Added a man page + +2008-05-22 Guillaume Chazarain + + * iotop/ui.py: Safer color terminal handling + +2008-05-22 Guillaume Chazarain + + * iotop/ui.py: Stop flickering during refresh + +2008-05-22 Guillaume Chazarain + + * NEWS, iotop/data.py, iotop/ui.py: Added workaround for missing + ac_etime in TASKSTATS_CMD_ATTR_TGID + +2008-04-20 Guillaume Chazarain + + * NEWS: Typo + +2008-04-20 Guillaume Chazarain + + * iotop/ui.py: Document the 'o' key. + +2008-04-20 Guillaume Chazarain + + * iotop/ui.py: Consistency in the grammar + +2008-04-20 Guillaume Chazarain + + * iotop/ui.py: Filter processes to display before trimming them to + avoid removing processes that would be displayed after the trimming. + For example, sorting by PID could place I/O active processes at the + end, but we don't want to delete them as they would be shown anyway + is -o is used. + +2008-04-06 Guillaume Chazarain + + * NEWS, iotop/ui.py: Typing 'p' dynamically toggle the --only option + +2008-03-20 Guillaume Chazarain + + * bin/iotop: Detect unsuccessful attempts at running an uninstalled + iotop + +2008-03-14 Guillaume Chazarain + + * iotop.py, run-iotop: Let's use the obvious filename + +2008-03-10 Guillaume Chazarain + + * release.sh: Remove blank line + +2008-03-10 Guillaume Chazarain + + * release.sh: Added release script + +2008-03-09 Guillaume Chazarain + + * MANIFEST.in, bin/iotop, setup.cfg, setup.py: Added packaging + information + +2008-03-09 Guillaume Chazarain + + * iotop/ui.py, iotop/version.py: Extracted out version number + +2008-03-09 Guillaume Chazarain + + * COPYING: Added GPLv2 COPYING file + +2008-03-09 Guillaume Chazarain + + * THANKS: Added THANKS file + +2008-03-09 Guillaume Chazarain + + * .gitignore: Ignore byte compiled files + +2008-03-09 Guillaume Chazarain + + * NEWS: Added NEWS file + +2008-03-09 Guillaume Chazarain + + * genetlink.py, iotop.py, iotop/__init__.py, iotop/data.py, + iotop/genetlink.py, iotop/netlink.py, iotop/ui.py, netlink.py, + run-iotop: Code reorganization + +2008-03-05 Guillaume Chazarain + + * genetlink.py, iotop.py, netlink.py: Instead of copy/pasting + pynl80211 in iotop.py, keep it in separate files + +2008-03-05 Guillaume Chazarain + + * iotop.py: Update e-mail and copyright information + +2008-03-05 Guillaume Chazarain + + * iotop.py: Reading the cmdline of a dead process raises an + exception too. Reported by Roland Kletzing + +2008-03-02 Guillaume Chazarain + + * iotop.py: Skip the dirname only when the cmdline starts with an + absolute path + +2008-01-20 Guillaume Chazarain + + * iotop.py: Fix the typo with the right correction this time + +2008-01-20 Guillaume Chazarain + + * iotop.py: Simplify help generation + +2008-01-18 Guillaume Chazarain + + * iotop.py: Bump version + +2008-01-18 Guillaume Chazarain + + * iotop.py: Removed embedded history comments as it is now in git. + +2008-01-18 Guillaume Chazarain + + * iotop.py: Added --only as suggested by Iain Lea + +2008-01-18 Guillaume Chazarain + + * iotop.py: Fix typo, reported by Iain Lea + +2007-12-19 Guillaume Chazarain + + * iotop.py: Tolerate misconfigured terminals + +2007-09-30 Guillaume Chazarain + + * iotop.py: Fixed -b + +2007-08-26 Guillaume Chazarain + + * iotop.py: Document taskstats bug: + http://lkml.org/lkml/2007/8/2/185 + +2007-08-25 Guillaume Chazarain + + * iotop.py: Handle terminal resizing + +2007-08-25 Guillaume Chazarain + + * iotop.py: More accurate cutting of the command line + +2007-08-19 Guillaume Chazarain + + * iotop.py: handle empty process list + +2007-08-19 Guillaume Chazarain + + * iotop.py: Fix "-P -p NOT_A_TGID", optimize -p + +2007-08-13 Guillaume Chazarain + + * iotop.py: Handle short replies, and fix bandwidth calculation when + delay != 1s + +2007-07-23 Guillaume Chazarain + + * iotop.py: Added support for taskstats version > 4 in iotop.py + +2007-07-15 Guillaume Chazarain + + * Initial import of iotop + diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..c3efed8 --- /dev/null +++ b/NEWS @@ -0,0 +1,79 @@ +0.6 +~~~ +o Clean up the terminal on exit +o Stop busy looping on exit in certain conditions +o Restored UTF-8 support with python2 +o Fixed install scripts to install to sbin/ instead of bin/ +o Releases are now gpg signed with key "4096R/4D23A27E 2013-05-26" + +0.5 +~~~ +o Adapt the display to the maximum pid width +o Include both total and actual disk bandwidth in the summary +o Conversion to Python 3 +o Installation to sbin instead of bin + +0.4.4 +~~~~~ +o Cosmetic fixes, including a better error message when missing root +credentials + +0.4.3 +~~~~~ +o Fixed netlink message parsing to accept alignement padding +o Force UTF-8 output even if the locale is misconfigured +o Changed the semantic of 'a' to show stats since iotop was started + +0.4.2 +~~~~~ +o Workaround for an ncurses bug where UTF-8 strings are misprinted +o Added ioprio support for the armel and hppa architectures +o Fix possible incompatibility with linux-2.6.37 on 64 bit systems +o Do not crash when the locale is incorrectly configured + +0.4.1 +~~~~~ +o Portability fix +o Better reporting of missing requirements + +0.4 +~~~ +o Compatibility with python2.4 using the ctypes module +o Stopped using setuptools in favor of straight distutils + +0.3.2 +~~~~~ +o Split long command lines in the middle instead of cutting them at the end +o Added a heuristic to detect kernels without CONFIG_TASK_DELAY_ACCT +o The I/O nice of a process/thread can be changed with the 'i' key + +0.3.1 +~~~~~ +o Fixed the ioprio syscall detection when running on i386/x86_64 +o Fixed interaction between --accumulated and --only +o Fixed column sorting with --accumulated + +0.3 +~~~ +o -P is now fully implemented and is dynamically toggled with 'p' +o Show the I/O priority +o Added the --accumulated, --kilobytes, --time and --quiet options + +0.2.1 +~~~~~ +o UTF-8 strings are now correctly handled + +0.2 +~~~ + +o Misconfigured terminals (TERM=xterm-color) are tolerated +o Added the --only option to only show processes or threads actually doing I/O +o Typing 'o' dynamically toggles the --only option +o Cosmetic fixes as well as minor bug fixes +o Re-organized code to import vanilla pynl80211 +o Added workaround KERNBUG display in -P + +0.1 +~~~ + +o First release diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..c33eb05 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 1.0 +Name: iotop +Version: 0.6 +Summary: Per process I/O bandwidth monitor +Home-page: http://guichaz.free.fr/iotop +Author: Guillaume Chazarain +Author-email: guichaz@gmail.com +License: GPL +Description: Iotop is a Python program with a top like UI used to show of behalf of which + process is the I/O going on. +Platform: UNKNOWN diff --git a/README b/README new file mode 100644 index 0000000..cf9e050 --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +Iotop is a Python program with a top like UI used to show of behalf of which +process is the I/O going on. It requires Python >= 2.7 and a Linux kernel >= +2.6.20 with the CONFIG_TASK_DELAY_ACCT CONFIG_TASKSTATS, +CONFIG_TASK_IO_ACCOUNTING and CONFIG_VM_EVENT_COUNTERS options on. + + +To run a local version of iotop: + +$ ./iotop.py + + +The documentation is available in the man page: + +$ man ./iotop.8 + + +To install iotop, you should use a package provided by your distribution. If you +really want to install this version of iotop on your system, do (as root): + +# ./setup.py install + + +-- +Guillaume Chazarain +http://guichaz.free.fr/iotop diff --git a/THANKS b/THANKS new file mode 100644 index 0000000..af1d6b2 --- /dev/null +++ b/THANKS @@ -0,0 +1,50 @@ +Iain Lea + Reported spelling errors, suggested --only. + +tropikhajma@gmail.com + Reported cosmetic bug: strip dirname from cmdline only on absolute paths. + +Roland Kletzing + Reported crashing bug: successfully opening /proc/PID/cmdline does not imply +we can read it (PID died between open() and read()). + +Paul Wise + Started the man page. + +Göran Uddeborg + Reported that iotop was misbehaving with UTF-8 strings. + +Ryan Lovett + Contributed a bugfix: flush the output in batch mode, so that a potential +output file is updated. + +Martin Bammer + Initial implementation of the --time and --quiet options. + +Piotr Engelking + Reported that iotop's ioprio_get syscall detection was buggy on 32bit +userspace on a x86_64 kernel. + +Gabriel Redner + Reported a crash were iotop could open /proc/PID/status but not read it as the process disappeared by then. + +Jiri Olsa + Wrote the support for python2.4 with the ctypes module. + +Philipp Thomas + Reported that iotop produced wrong numbers in openSUSE 11.3. + +Jakub Wilk + Contributed syscall numbers for getting and setting IO priority on armel and hppa architectures. + +Jeff Mahoney + Contributed a fixed implementation of the taskstats parsing code. + +Florian Mickler + Contributed a fixed implementation of the taskstats parsing code. + +Ka-Hing Cheung + Contributed code to show custom thread names. + +Igor Bazhitov + Contributed the differentiation between total and actual I/O. diff --git a/iotop.8 b/iotop.8 new file mode 100644 index 0000000..68d9edd --- /dev/null +++ b/iotop.8 @@ -0,0 +1,97 @@ +.\" Debian manual page, has been forwarded upstream +.TH IOTOP "8" "April 2009" +.SH NAME +iotop \- simple top\-like I/O monitor +.SH SYNOPSIS +.B iotop +[\fIOPTIONS\fR] +.SH DESCRIPTION +iotop watches I/O usage information output by the Linux kernel (requires +2.6.20 or later) and displays a table of current I/O usage by processes +or threads on the system. At least the CONFIG_TASK_DELAY_ACCT, +CONFIG_TASK_IO_ACCOUNTING, CONFIG_TASKSTATS and CONFIG_VM_EVENT_COUNTERS +options need to be enabled in your Linux kernel build configuration. +.PP +iotop displays columns for the I/O bandwidth read and written by each +process/thread during the sampling period. It also displays the percentage +of time the thread/process spent while swapping in and while waiting on I/O. For each process, its I/O priority (class/level) is shown. +.PP +In addition, the total I/O bandwidth read and written during the sampling +period is displayed at the top of the interface. +\fBTotal DISK READ\fR and \fBTotal DISK WRITE\fR values represent total read +and write bandwidth between processes and kernel threads on the one side and +kernel block device subsystem on the other. While \fBActual DISK READ\fR and +\fBActual DISK WRITE\fR values represent corresponding bandwidths for actual +disk I/O between kernel block device subsystem and underlying hardware (HDD, SSD, etc.). +Thus \fBTotal\fR and \fBActual\fR values may not be equal at any given moment of time +due to data caching and I/O operations reordering that take place inside Linux kernel. +.PP +Use the left and right arrows to change the sorting, r to reverse the +sorting order, o to toggle the \-\-only option, p to toggle the \-\-processes option, a to toggle the \-\-accumulated option, q to quit or i to change the priority of a thread or a process' thread(s). Any other key will force a refresh. +.SH OPTIONS +.TP +\fB\-\-version\fR +Show the version number and exit +.TP +\fB\-h\fR, \fB\-\-help\fR +Show usage information and exit +.TP +\fB\-o\fR, \fB\-\-only\fR +Only show processes or threads actually doing I/O, instead of showing all processes or threads. This can be dynamically toggled by pressing o. +.TP +\fB\-b\fR, \fB\-\-batch\fR +Turn on non\-interactive mode. +Useful for logging I/O usage over time. +.TP +\fB\-n\fR NUM, \fB\-\-iter\fR=\fINUM\fR +Set the number of iterations before quitting (never quit by default). +This is most useful in non\-interactive mode. +.TP +\fB\-d\fR SEC, \fB\-\-delay\fR=\fISEC\fR +Set the delay between iterations in seconds (1 second by default). +Accepts non-integer values such as 1.1 seconds. +.TP +\fB\-p\fR PID, \fB\-\-pid\fR=\fIPID\fR +A list of processes/threads to monitor (all by default). +.TP +\fB\-u\fR USER, \fB\-\-user\fR=\fIUSER\fR +A list of users to monitor (all by default) +.TP +\fB\-P\fR, \fB\-\-processes\fR +Only show processes. Normally iotop shows all threads. +.TP +\fB\-a\fR, \fB\-\-accumulated\fR +Show accumulated I/O instead of bandwidth. In this mode, iotop shows the amount of I/O processes have done since iotop started. +.TP +\fB\-k\fR, \fB\-\-kilobytes\fR +Use kilobytes instead of a human friendly unit. This mode is useful when scripting the batch mode of iotop. Instead of choosing the most appropriate unit iotop will display all sizes in kilobytes. +.TP +\fB\-t\fR, \fB\-\-time\fR +Add a timestamp on each line (implies \-\-batch). Each line will be prefixed by the current time. +.TP +\fB\-q\fR, \fB\-\-quiet\fR +suppress some lines of header (implies \-\-batch). This option can be specified up to three times to remove header lines. +.RS +.PD 0 +.TP +.B \-q +column names are only printed on the first iteration, +.TP +.B \-qq +column names are never printed, +.TP +.B \-qqq +the I/O summary is never printed. +.PD 1 +.RE +.SH SEE ALSO +.BR ionice (1), +.BR top (1), +.BR vmstat (1), +.BR atop (1), +.BR htop (1) +.SH AUTHOR +iotop was written by Guillaume Chazarain. +.PP +This manual page was started by Paul Wise for the +Debian project and is placed in the public domain. diff --git a/iotop.py b/iotop.py new file mode 100755 index 0000000..13ed873 --- /dev/null +++ b/iotop.py @@ -0,0 +1,15 @@ +#!/usr/bin/python +# iotop: Display I/O usage of processes in a top like UI +# Copyright (c) 2007, 2008 Guillaume Chazarain +# GPL version 2 or later +# See iotop --help for some help + +import sys + +from iotop.ui import main + +try: + main() +except KeyboardInterrupt: + pass +sys.exit(0) diff --git a/iotop/__init__.py b/iotop/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/iotop/__init__.py diff --git a/iotop/data.py b/iotop/data.py new file mode 100644 index 0000000..25b659e --- /dev/null +++ b/iotop/data.py @@ -0,0 +1,455 @@ +# 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 Library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# See the COPYING file for license information. +# +# Copyright (c) 2007 Guillaume Chazarain + +# Allow printing with same syntax in Python 2/3 +from __future__ import print_function + +import errno +import os +import pprint +import pwd +import stat +import struct +import sys +import time + +# +# Check for requirements: +# o Linux >= 2.6.20 with I/O accounting and VM event counters +# + +ioaccounting = os.path.exists('/proc/self/io') + +try: + from iotop.vmstat import VmStat + vmstat_f = VmStat() +except: + vm_event_counters = False +else: + vm_event_counters = True + +if not ioaccounting or not vm_event_counters: + print('Could not run iotop as some of the requirements are not met:') + print('- Linux >= 2.6.20 with') + if not ioaccounting: + print(' - I/O accounting support ' \ + '(CONFIG_TASKSTATS, CONFIG_TASK_DELAY_ACCT, ' \ + 'CONFIG_TASK_IO_ACCOUNTING)') + if not vm_event_counters: + print(' - VM event counters (CONFIG_VM_EVENT_COUNTERS)') + sys.exit(1) + +from iotop import ioprio, vmstat +from iotop.netlink import Connection, NETLINK_GENERIC, U32Attr, NLM_F_REQUEST +from iotop.genetlink import Controller, GeNlMessage + +class DumpableObject(object): + """Base class for all objects that allows easy introspection when printed""" + def __repr__(self): + return '%s: %s>' % (str(type(self))[:-1], pprint.pformat(self.__dict__)) + + +# +# Interesting fields in a taskstats output +# + +class Stats(DumpableObject): + members_offsets = [ + ('blkio_delay_total', 40), + ('swapin_delay_total', 56), + ('read_bytes', 248), + ('write_bytes', 256), + ('cancelled_write_bytes', 264) + ] + + has_blkio_delay_total = False + + def __init__(self, task_stats_buffer): + sd = self.__dict__ + for name, offset in Stats.members_offsets: + data = task_stats_buffer[offset:offset + 8] + sd[name] = struct.unpack('Q', data)[0] + + # This is a heuristic to detect if CONFIG_TASK_DELAY_ACCT is enabled in + # the kernel. + if not Stats.has_blkio_delay_total: + Stats.has_blkio_delay_total = self.blkio_delay_total != 0 + + def accumulate(self, other_stats, destination, coeff=1): + """Update destination from operator(self, other_stats)""" + dd = destination.__dict__ + sd = self.__dict__ + od = other_stats.__dict__ + for member, offset in Stats.members_offsets: + dd[member] = sd[member] + coeff * od[member] + + def delta(self, other_stats, destination): + """Update destination with self - other_stats""" + return self.accumulate(other_stats, destination, coeff=-1) + + def is_all_zero(self): + sd = self.__dict__ + for name, offset in Stats.members_offsets: + if sd[name] != 0: + return False + return True + + @staticmethod + def build_all_zero(): + stats = Stats.__new__(Stats) + std = stats.__dict__ + for name, offset in Stats.members_offsets: + std[name] = 0 + return stats + +# +# Netlink usage for taskstats +# + +TASKSTATS_CMD_GET = 1 +TASKSTATS_CMD_ATTR_PID = 1 +TASKSTATS_TYPE_AGGR_PID = 4 +TASKSTATS_TYPE_PID = 1 +TASKSTATS_TYPE_STATS = 3 + +class TaskStatsNetlink(object): + # Keep in sync with format_stats() and pinfo.did_some_io() + + def __init__(self, options): + self.options = options + self.connection = Connection(NETLINK_GENERIC) + controller = Controller(self.connection) + self.family_id = controller.get_family_id('TASKSTATS') + + def build_request(self, tid): + return GeNlMessage(self.family_id, cmd=TASKSTATS_CMD_GET, + attrs=[U32Attr(TASKSTATS_CMD_ATTR_PID, tid)], + flags=NLM_F_REQUEST) + + def get_single_task_stats(self, thread): + thread.task_stats_request.send(self.connection) + try: + reply = GeNlMessage.recv(self.connection) + except OSError as e: + if e.errno == errno.ESRCH: + # OSError: Netlink error: No such process (3) + return + raise + for attr_type, attr_value in reply.attrs.items(): + if attr_type == TASKSTATS_TYPE_AGGR_PID: + reply = attr_value.nested() + break + else: + return + taskstats_data = reply[TASKSTATS_TYPE_STATS].data + if len(taskstats_data) < 272: + # Short reply + return + taskstats_version = struct.unpack('H', taskstats_data[:2])[0] + assert taskstats_version >= 4 + return Stats(taskstats_data) + +# +# PIDs manipulations +# + +def find_uids(options): + """Build options.uids from options.users by resolving usernames to UIDs""" + options.uids = [] + error = False + for u in options.users or []: + try: + uid = int(u) + except ValueError: + try: + passwd = pwd.getpwnam(u) + except KeyError: + print('Unknown user:', u, file=sys.stderr) + error = True + else: + uid = passwd.pw_uid + if not error: + options.uids.append(uid) + if error: + sys.exit(1) + + +def parse_proc_pid_status(pid): + result_dict = {} + try: + for line in open('/proc/%d/status' % pid): + key, value = line.split(':\t', 1) + result_dict[key] = value.strip() + except IOError: + pass # No such process + return result_dict + + +def safe_utf8_decode(s): + try: + return s.decode('utf-8') + except UnicodeDecodeError: + return s.encode('string_escape') + except AttributeError: + return s + +class ThreadInfo(DumpableObject): + """Stats for a single thread""" + def __init__(self, tid, taskstats_connection): + self.tid = tid + self.mark = True + self.stats_total = None + self.stats_delta = Stats.__new__(Stats) + self.task_stats_request = taskstats_connection.build_request(tid) + + def get_ioprio(self): + return ioprio.get(self.tid) + + def set_ioprio(self, ioprio_class, ioprio_data): + return ioprio.set_ioprio(ioprio.IOPRIO_WHO_PROCESS, self.tid, + ioprio_class, ioprio_data) + + def update_stats(self, stats): + if not self.stats_total: + self.stats_total = stats + stats.delta(self.stats_total, self.stats_delta) + self.stats_total = stats + + +class ProcessInfo(DumpableObject): + """Stats for a single process (a single line in the output): if + options.processes is set, it is a collection of threads, otherwise a single + thread.""" + def __init__(self, pid): + self.pid = pid + self.uid = None + self.user = None + self.threads = {} # {tid: ThreadInfo} + self.stats_delta = Stats.build_all_zero() + self.stats_accum = Stats.build_all_zero() + self.stats_accum_timestamp = time.time() + + def is_monitored(self, options): + if (options.pids and not options.processes and + self.pid not in options.pids): + # We only monitor some threads, not this one + return False + + if options.uids and self.get_uid() not in options.uids: + # We only monitor some users, not this one + return False + + return True + + def get_uid(self): + if self.uid: + return self.uid + # uid in (None, 0) means either we don't know the UID yet or the process + # runs as root so it can change its UID. In both cases it means we have + # to find out its current UID. + try: + uid = os.stat('/proc/%d' % self.pid)[stat.ST_UID] + except OSError: + # The process disappeared + uid = None + if uid != self.uid: + # Maybe the process called setuid() + self.user = None + self.uid = uid + return uid + + def get_user(self): + uid = self.get_uid() + if uid is not None and not self.user: + try: + self.user = safe_utf8_decode(pwd.getpwuid(uid).pw_name) + except (KeyError, AttributeError): + self.user = str(uid) + return self.user or '{none}' + + def get_cmdline(self): + # A process may exec, so we must always reread its cmdline + try: + proc_cmdline = open('/proc/%d/cmdline' % self.pid) + cmdline = proc_cmdline.read(4096) + except IOError: + return '{no such process}' + proc_status = parse_proc_pid_status(self.pid) + if not cmdline: + # Probably a kernel thread, get its name from /proc/PID/status + proc_status_name = proc_status.get('Name', '') + if proc_status_name: + proc_status_name = '[%s]' % proc_status_name + else: + proc_status_name = '{no name}' + return proc_status_name + suffix = '' + tgid = int(proc_status.get('Tgid', self.pid)) + if tgid != self.pid: + # Not the main thread, maybe it has a custom name + tgid_name = parse_proc_pid_status(tgid).get('Name', '') + thread_name = proc_status.get('Name', '') + if thread_name != tgid_name: + suffix += ' [%s]' % thread_name + parts = cmdline.split('\0') + if parts[0].startswith('/'): + first_command_char = parts[0].rfind('/') + 1 + parts[0] = parts[0][first_command_char:] + cmdline = ' '.join(parts).strip() + return safe_utf8_decode(cmdline + suffix) + + def did_some_io(self, accumulated): + if accumulated: + return not self.stats_accum.is_all_zero() + for t in self.threads.values(): + if not t.stats_delta.is_all_zero(): + return True + return False + + def get_ioprio(self): + priorities = set(t.get_ioprio() for t in self.threads.values()) + if len(priorities) == 1: + return priorities.pop() + return '?dif' + + def set_ioprio(self, ioprio_class, ioprio_data): + for thread in self.threads.values(): + thread.set_ioprio(ioprio_class, ioprio_data) + + def ioprio_sort_key(self): + return ioprio.sort_key(self.get_ioprio()) + + def get_thread(self, tid, taskstats_connection): + thread = self.threads.get(tid, None) + if not thread: + thread = ThreadInfo(tid, taskstats_connection) + self.threads[tid] = thread + return thread + + def update_stats(self): + stats_delta = Stats.build_all_zero() + for tid, thread in self.threads.items(): + if not thread.mark: + stats_delta.accumulate(thread.stats_delta, stats_delta) + self.threads = dict([(tid, thread) for tid, thread in + self.threads.items() if not thread.mark]) + + nr_threads = len(self.threads) + if not nr_threads: + return False + + stats_delta.blkio_delay_total /= nr_threads + stats_delta.swapin_delay_total /= nr_threads + + self.stats_delta = stats_delta + self.stats_accum.accumulate(self.stats_delta, self.stats_accum) + + return True + +class ProcessList(DumpableObject): + def __init__(self, taskstats_connection, options): + # {pid: ProcessInfo} + self.processes = {} + self.taskstats_connection = taskstats_connection + self.options = options + self.timestamp = time.time() + self.vmstat = vmstat.VmStat() + + # A first time as we are interested in the delta + self.update_process_counts() + + def get_process(self, pid): + """Either get the specified PID from self.processes or build a new + ProcessInfo if we see this PID for the first time""" + process = self.processes.get(pid, None) + if not process: + process = ProcessInfo(pid) + self.processes[pid] = process + + if process.is_monitored(self.options): + return process + + def list_tgids(self): + if self.options.pids: + return self.options.pids + + tgids = os.listdir('/proc') + if self.options.processes: + return [int(tgid) for tgid in tgids if '0' <= tgid[0] <= '9'] + + tids = [] + for tgid in tgids: + if '0' <= tgid[0] <= '9': + try: + tids.extend(map(int, os.listdir('/proc/' + tgid + '/task'))) + except OSError: + # The PID went away + pass + return tids + + def list_tids(self, tgid): + if not self.options.processes: + return [tgid] + + try: + tids = list(map(int, os.listdir('/proc/%d/task' % tgid))) + except OSError: + return [] + + if self.options.pids: + tids = list(set(self.options.pids).intersection(set(tids))) + + return tids + + def update_process_counts(self): + new_timestamp = time.time() + self.duration = new_timestamp - self.timestamp + self.timestamp = new_timestamp + + total_read = total_write = 0 + + for tgid in self.list_tgids(): + process = self.get_process(tgid) + if not process: + continue + for tid in self.list_tids(tgid): + thread = process.get_thread(tid, self.taskstats_connection) + stats = self.taskstats_connection.get_single_task_stats(thread) + if stats: + thread.update_stats(stats) + delta = thread.stats_delta + total_read += delta.read_bytes + total_write += delta.write_bytes + thread.mark = False + return (total_read, total_write), self.vmstat.delta() + + def refresh_processes(self): + for process in self.processes.values(): + for thread in process.threads.values(): + thread.mark = True + + total_read_and_write = self.update_process_counts() + + self.processes = dict([(pid, process) for pid, process in + self.processes.items() if + process.update_stats()]) + + return total_read_and_write + + def clear(self): + self.processes = {} diff --git a/iotop/genetlink.py b/iotop/genetlink.py new file mode 100644 index 0000000..66f3d02 --- /dev/null +++ b/iotop/genetlink.py @@ -0,0 +1,73 @@ +''' +Netlink message generation/parsing + +Copyright 2007 Johannes Berg + +GPLv2+; See copying for details. +''' + +import struct +from iotop.netlink import NLM_F_REQUEST, NLMSG_MIN_TYPE, Message, parse_attributes +from iotop.netlink import NulStrAttr, Connection, NETLINK_GENERIC + +CTRL_CMD_UNSPEC = 0 +CTRL_CMD_NEWFAMILY = 1 +CTRL_CMD_DELFAMILY = 2 +CTRL_CMD_GETFAMILY = 3 +CTRL_CMD_NEWOPS = 4 +CTRL_CMD_DELOPS = 5 +CTRL_CMD_GETOPS = 6 + +CTRL_ATTR_UNSPEC = 0 +CTRL_ATTR_FAMILY_ID = 1 +CTRL_ATTR_FAMILY_NAME = 2 +CTRL_ATTR_VERSION = 3 +CTRL_ATTR_HDRSIZE = 4 +CTRL_ATTR_MAXATTR = 5 +CTRL_ATTR_OPS = 6 + +class GenlHdr: + def __init__(self, cmd, version = 0): + self.cmd = cmd + self.version = version + def _dump(self): + return struct.pack("BBxx", self.cmd, self.version) + +def _genl_hdr_parse(data): + return GenlHdr(*struct.unpack("BBxx", data)) + +GENL_ID_CTRL = NLMSG_MIN_TYPE + +class GeNlMessage(Message): + def __init__(self, family, cmd, attrs=[], flags=0): + self.cmd = cmd + self.attrs = attrs + self.family = family + Message.__init__(self, family, flags=flags, + payload=[GenlHdr(self.cmd)]+attrs) + + @staticmethod + def recv(conn): + msg = conn.recv() + packet = msg.payload + hdr = _genl_hdr_parse(packet[:4]) + + genlmsg = GeNlMessage(msg.type, hdr.cmd, [], msg.flags) + genlmsg.attrs = parse_attributes(packet[4:]) + genlmsg.version = hdr.version + + return genlmsg + +class Controller: + def __init__(self, conn): + self.conn = conn + def get_family_id(self, family): + a = NulStrAttr(CTRL_ATTR_FAMILY_NAME, family) + m = GeNlMessage(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, + flags=NLM_F_REQUEST, attrs=[a]) + m.send(self.conn) + m = GeNlMessage.recv(self.conn) + return m.attrs[CTRL_ATTR_FAMILY_ID].u16() + +connection = Connection(NETLINK_GENERIC) +controller = Controller(connection) diff --git a/iotop/ioprio.py b/iotop/ioprio.py new file mode 100644 index 0000000..85957fd --- /dev/null +++ b/iotop/ioprio.py @@ -0,0 +1,180 @@ +# 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 Library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# See the COPYING file for license information. +# +# Copyright (c) 2007 Guillaume Chazarain + +import ctypes +import fnmatch +import os +import platform + +# From http://git.kernel.org/?p=utils/util-linux-ng/util-linux-ng.git;a=blob; +# f=configure.ac;h=770eb45ae85d32757fc3cff1d70a7808a627f9f7;hb=HEAD#l354 +# i386 bit userspace under an x86_64 kernel will have its uname() appear as +# 'x86_64' but it will use the i386 syscall number, that's why we consider both +# the architecture name and the word size. +IOPRIO_GET_ARCH_SYSCALL = [ + ('alpha', '*', 443), + ('arm*', '*', 315), + ('i*86', '*', 290), + ('ia64*', '*', 1275), + ('parisc*', '*', 268), + ('powerpc*', '*', 274), + ('s390*', '*', 283), + ('sparc*', '*', 218), + ('sh*', '*', 289), + ('x86_64*', '32bit', 290), + ('x86_64*', '64bit', 252), +] + +IOPRIO_SET_ARCH_SYSCALL = [ + ('alpha', '*', 442), + ('arm*', '*', 314), + ('i*86', '*', 289), + ('ia64*', '*', 1274), + ('parisc*', '*', 267), + ('powerpc*', '*', 273), + ('s390*', '*', 282), + ('sparc*', '*', 196), + ('sh*', '*', 288), + ('x86_64*', '32bit', 289), + ('x86_64*', '64bit', 251), +] + +def find_ioprio_syscall_number(syscall_list): + arch = os.uname()[4] + bits = platform.architecture()[0] + + for candidate_arch, candidate_bits, syscall_nr in syscall_list: + if fnmatch.fnmatch(arch, candidate_arch) and \ + fnmatch.fnmatch(bits, candidate_bits): + return syscall_nr + +class IoprioSetError(Exception): + def __init__(self, err): + try: + self.err = os.strerror(err) + except TypeError: + self.err = err + +__NR_ioprio_get = find_ioprio_syscall_number(IOPRIO_GET_ARCH_SYSCALL) +__NR_ioprio_set = find_ioprio_syscall_number(IOPRIO_SET_ARCH_SYSCALL) + +try: + ctypes_handle = ctypes.CDLL(None, use_errno=True) +except TypeError: + ctypes_handle = ctypes.CDLL(None) + +syscall = ctypes_handle.syscall + +PRIORITY_CLASSES = [None, 'rt', 'be', 'idle'] + +IOPRIO_WHO_PROCESS = 1 +IOPRIO_CLASS_SHIFT = 13 +IOPRIO_PRIO_MASK = (1 << IOPRIO_CLASS_SHIFT) - 1 + +def ioprio_value(ioprio_class, ioprio_data): + try: + ioprio_class = PRIORITY_CLASSES.index(ioprio_class) + except ValueError: + ioprio_class = PRIORITY_CLASSES.index(None) + return (ioprio_class << IOPRIO_CLASS_SHIFT) | ioprio_data + +def ioprio_class(ioprio): + return PRIORITY_CLASSES[ioprio >> IOPRIO_CLASS_SHIFT] + +def ioprio_data(ioprio): + return ioprio & IOPRIO_PRIO_MASK + +sched_getscheduler = ctypes_handle.sched_getscheduler +SCHED_OTHER, SCHED_FIFO, SCHED_RR, SCHED_BATCH, SCHED_ISO, SCHED_IDLE = range(6) + +getpriority = ctypes_handle.getpriority +PRIO_PROCESS = 0 + +def get_ioprio_from_sched(pid): + scheduler = sched_getscheduler(pid) + nice = getpriority(PRIO_PROCESS, pid) + ioprio_nice = (nice + 20) / 5 + + if scheduler in (SCHED_FIFO, SCHED_RR): + return 'rt/%d' % ioprio_nice + elif scheduler == SCHED_IDLE: + return 'idle' + else: + return 'be/%d' % ioprio_nice + +def get(pid): + if __NR_ioprio_get is None: + return '?sys' + + ioprio = syscall(__NR_ioprio_get, IOPRIO_WHO_PROCESS, pid) + if ioprio < 0: + return '?err' + + prio_class = ioprio_class(ioprio) + if not prio_class: + return get_ioprio_from_sched(pid) + if prio_class == 'idle': + return prio_class + return '%s/%d' % (prio_class, ioprio_data(ioprio)) + +def set_ioprio(which, who, ioprio_class, ioprio_data): + if __NR_ioprio_set is None: + raise IoprioSetError('No ioprio_set syscall found') + + ioprio_val = ioprio_value(ioprio_class, ioprio_data) + ret = syscall(__NR_ioprio_set, which, who, ioprio_val, use_errno=True) + if ret < 0: + try: + err = ctypes.get_errno() + except AttributeError: + err = 'Unknown error (errno support not available before Python2.6)' + raise IoprioSetError(err) + +def sort_key(key): + if key[0] == '?': + return -ord(key[1]) + + if '/' in key: + if key.startswith('rt/'): + shift = 0 + elif key.startswith('be/'): + shift = 1 + prio = int(key.split('/')[1]) + elif key == 'idle': + shift = 2 + prio = 0 + + return (1 << (shift * IOPRIO_CLASS_SHIFT)) + prio + +def to_class_and_data(ioprio_str): + if '/' in ioprio_str: + split = ioprio_str.split('/') + return (split[0], int(split[1])) + elif ioprio_str == 'idle': + return ('idle', 0) + return (None, None) + +if __name__ == '__main__': + import sys + if len(sys.argv) == 2: + pid = int(sys.argv[1]) + else: + pid = os.getpid() + print('pid:', pid) + print('ioprio:', get(pid)) + diff --git a/iotop/netlink.py b/iotop/netlink.py new file mode 100644 index 0000000..bd41348 --- /dev/null +++ b/iotop/netlink.py @@ -0,0 +1,242 @@ +''' +Netlink message generation/parsing + +Copyright 2007 Johannes Berg + +GPLv2+; See copying for details. +''' + +import os +import socket +import struct + +try: + # try to use python 2.5's netlink support + _dummysock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 0) + _dummysock.bind((0, 0)) + del _dummysock + def _nl_bind(descriptor, addr): + descriptor.bind(addr) + def _nl_getsockname(descriptor): + return descriptor.getsockname() + def _nl_send(descriptor, msg): + descriptor.send(msg) + def _nl_recv(descriptor, bufs=16384): + return descriptor.recvfrom(bufs) +except socket.error: + # or fall back to the _netlink C module + try: + import _netlink + def _nl_bind(descriptor, addr): + _netlink.bind(descriptor.fileno(), addr[1]) + def _nl_getsockname(descriptor): + return _netlink.getsockname(descriptor.fileno()) + def _nl_send(descriptor, msg): + _netlink.send(descriptor.fileno(), msg) + def _nl_recv(descriptor, bufs=16384): + return _netlink.recvfrom(descriptor.fileno(), bufs) + except ImportError: + # or fall back to the ctypes module + import ctypes + + libc = ctypes.CDLL(None) + + class SOCKADDR_NL(ctypes.Structure): + _fields_ = [("nl_family", ctypes.c_ushort), + ("nl_pad", ctypes.c_ushort), + ("nl_pid", ctypes.c_int), + ("nl_groups", ctypes.c_int)] + + def _nl_bind(descriptor, addr): + addr = SOCKADDR_NL(socket.AF_NETLINK, 0, os.getpid(), 0) + return libc.bind(descriptor.fileno(), + ctypes.pointer(addr), + ctypes.sizeof(addr)) + + def _nl_getsockname(descriptor): + addr = SOCKADDR_NL(0, 0, 0, 0) + len = ctypes.c_int(ctypes.sizeof(addr)); + libc.getsockname(descriptor.fileno(), + ctypes.pointer(addr), + ctypes.pointer(len)) + return addr.nl_pid, addr.nl_groups; + + def _nl_send(descriptor, msg): + return libc.send(descriptor.fileno(), msg, len(msg), 0); + + def _nl_recv(descriptor, bufs=16384): + addr = SOCKADDR_NL(0, 0, 0, 0) + len = ctypes.c_int(ctypes.sizeof(addr)) + buf = ctypes.create_string_buffer(bufs) + + r = libc.recvfrom(descriptor.fileno(), + buf, bufs, 0, + ctypes.pointer(addr), ctypes.pointer(len)) + + ret = ctypes.string_at(ctypes.pointer(buf), r) + return ret, (addr.nl_pid, addr.nl_groups) + + +# flags +NLM_F_REQUEST = 1 +NLM_F_MULTI = 2 +NLM_F_ACK = 4 +NLM_F_ECHO = 8 + +# types +NLMSG_NOOP = 1 +NLMSG_ERROR = 2 +NLMSG_DONE = 3 +NLMSG_OVERRUN = 4 +NLMSG_MIN_TYPE = 0x10 + +class Attr: + def __init__(self, attr_type, data, *values): + self.type = attr_type + if len(values): + self.data = struct.pack(data, *values) + else: + self.data = data + + def _dump(self): + hdr = struct.pack("HH", len(self.data)+4, self.type) + length = len(self.data) + pad = ((length + 4 - 1) & ~3 ) - length + return hdr + self.data + b'\0' * pad + + def __repr__(self): + return '' % (self.type, repr(self.data)) + + def u16(self): + return struct.unpack('H', self.data)[0] + def s16(self): + return struct.unpack('h', self.data)[0] + def u32(self): + return struct.unpack('I', self.data)[0] + def s32(self): + return struct.unpack('i', self.data)[0] + def str(self): + return self.data + def nulstr(self): + return self.data.split('\0')[0] + def nested(self): + return parse_attributes(self.data) + +class StrAttr(Attr): + def __init__(self, attr_type, data): + Attr.__init__(self, attr_type, "%ds" % len(data), data.encode('utf-8')) + +class NulStrAttr(Attr): + def __init__(self, attr_type, data): + Attr.__init__(self, attr_type, "%dsB" % len(data), data.encode('utf-8'), 0) + +class U32Attr(Attr): + def __init__(self, attr_type, val): + Attr.__init__(self, attr_type, "I", val) + +class U8Attr(Attr): + def __init__(self, attr_type, val): + Attr.__init__(self, attr_type, "B", val) + +class Nested(Attr): + def __init__(self, attr_type, attrs): + self.attrs = attrs + self.type = attr_type + + def _dump(self): + contents = [] + for attr in self.attrs: + contents.append(attr._dump()) + contents = ''.join(contents) + length = len(contents) + hdr = struct.pack("HH", length+4, self.type) + return hdr + contents + +NETLINK_ROUTE = 0 +NETLINK_UNUSED = 1 +NETLINK_USERSOCK = 2 +NETLINK_FIREWALL = 3 +NETLINK_INET_DIAG = 4 +NETLINK_NFLOG = 5 +NETLINK_XFRM = 6 +NETLINK_SELINUX = 7 +NETLINK_ISCSI = 8 +NETLINK_AUDIT = 9 +NETLINK_FIB_LOOKUP = 10 +NETLINK_CONNECTOR = 11 +NETLINK_NETFILTER = 12 +NETLINK_IP6_FW = 13 +NETLINK_DNRTMSG = 14 +NETLINK_KOBJECT_UEVENT = 15 +NETLINK_GENERIC = 16 + +class Message: + def __init__(self, msg_type, flags=0, seq=-1, payload=None): + self.type = msg_type + self.flags = flags + self.seq = seq + self.pid = -1 + payload = payload or [] + if isinstance(payload, list): + contents = [] + for attr in payload: + contents.append(attr._dump()) + self.payload = b''.join(contents) + else: + self.payload = payload + + def send(self, conn): + if self.seq == -1: + self.seq = conn.seq() + + self.pid = conn.pid + length = len(self.payload) + + hdr = struct.pack("IHHII", length + 4*4, self.type, + self.flags, self.seq, self.pid) + conn.send(hdr + self.payload) + + def __repr__(self): + return '' % ( + self.type, self.pid, self.seq, self.flags, repr(self.payload)) + +class Connection: + def __init__(self, nltype, groups=0, unexpected_msg_handler=None): + self.descriptor = socket.socket(socket.AF_NETLINK, + socket.SOCK_RAW, nltype) + self.descriptor.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536) + self.descriptor.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536) + _nl_bind(self.descriptor, (0, groups)) + self.pid, self.groups = _nl_getsockname(self.descriptor) + self._seq = 0 + self.unexpected = unexpected_msg_handler + def send(self, msg): + _nl_send(self.descriptor, msg) + def recv(self): + contents, (nlpid, nlgrps) = _nl_recv(self.descriptor) + # XXX: python doesn't give us message flags, check + # len(contents) vs. msglen for TRUNC + msglen, msg_type, flags, seq, pid = struct.unpack("IHHII", + contents[:16]) + msg = Message(msg_type, flags, seq, contents[16:]) + msg.pid = pid + if msg.type == NLMSG_ERROR: + errno = -struct.unpack("i", msg.payload[:4])[0] + if errno != 0: + err = OSError("Netlink error: %s (%d)" % ( + os.strerror(errno), errno)) + err.errno = errno + raise err + return msg + def seq(self): + self._seq += 1 + return self._seq + +def parse_attributes(data): + attrs = {} + while len(data): + attr_len, attr_type = struct.unpack("HH", data[:4]) + attrs[attr_type] = Attr(attr_type, data[4:attr_len]) + attr_len = ((attr_len + 4 - 1) & ~3 ) + data = data[attr_len:] + return attrs diff --git a/iotop/ui.py b/iotop/ui.py new file mode 100644 index 0000000..e033c92 --- /dev/null +++ b/iotop/ui.py @@ -0,0 +1,621 @@ +# 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 Library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# See the COPYING file for license information. +# +# Copyright (c) 2007 Guillaume Chazarain + +# Allow printing with same syntax in Python 2/3 +from __future__ import print_function + +import curses +import errno +import locale +import math +import optparse +import os +import select +import signal +import sys +import time + +from iotop.data import find_uids, TaskStatsNetlink, ProcessList, Stats +from iotop.data import ThreadInfo +from iotop.version import VERSION +from iotop import ioprio +from iotop.ioprio import IoprioSetError + +# +# Utility functions for the UI +# + +UNITS = ['B', 'K', 'M', 'G', 'T', 'P', 'E'] + +def human_size(size): + if size > 0: + sign = '' + elif size < 0: + sign = '-' + size = -size + else: + return '0.00 B' + + expo = int(math.log(size / 2, 2) / 10) + return '%s%.2f %s' % (sign, (float(size) / (1 << (10 * expo))), UNITS[expo]) + +def format_size(options, bytes): + if options.kilobytes: + return '%.2f K' % (bytes / 1024.0) + return human_size(bytes) + +def format_bandwidth(options, size, duration): + return format_size(options, size and float(size) / duration) + '/s' + +def format_stats(options, process, duration): + # Keep in sync with TaskStatsNetlink.members_offsets and + # IOTopUI.get_data(self) + def delay2percent(delay): # delay in ns, duration in s + return '%.2f %%' % min(99.99, delay / (duration * 10000000.0)) + if options.accumulated: + stats = process.stats_accum + display_format = lambda size, duration: format_size(options, size) + duration = time.time() - process.stats_accum_timestamp + else: + stats = process.stats_delta + display_format = lambda size, duration: format_bandwidth( + options, size, duration) + io_delay = delay2percent(stats.blkio_delay_total) + swapin_delay = delay2percent(stats.swapin_delay_total) + read_bytes = display_format(stats.read_bytes, duration) + written_bytes = stats.write_bytes - stats.cancelled_write_bytes + written_bytes = max(0, written_bytes) + write_bytes = display_format(written_bytes, duration) + return io_delay, swapin_delay, read_bytes, write_bytes + +def get_max_pid_width(): + try: + return len(open('/proc/sys/kernel/pid_max').read().strip()) + except Exception as e: + print(e) + # Reasonable default in case something fails + return 5 + +MAX_PID_WIDTH = get_max_pid_width() + +# +# UI Exceptions +# + +class CancelInput(Exception): pass +class InvalidInt(Exception): pass +class InvalidPid(Exception): pass +class InvalidTid(Exception): pass +class InvalidIoprioData(Exception): pass + +# +# The UI +# + +class IOTopUI(object): + # key, reverse + sorting_keys = [ + (lambda p, s: p.pid, False), + (lambda p, s: p.ioprio_sort_key(), False), + (lambda p, s: p.get_user(), False), + (lambda p, s: s.read_bytes, True), + (lambda p, s: s.write_bytes - s.cancelled_write_bytes, True), + (lambda p, s: s.swapin_delay_total, True), + # The default sorting (by I/O % time) should show processes doing + # only writes, without waiting on them + (lambda p, s: s.blkio_delay_total or + int(not(not(s.read_bytes or s.write_bytes))), True), + (lambda p, s: p.get_cmdline(), False), + ] + + def __init__(self, win, process_list, options): + self.process_list = process_list + self.options = options + self.sorting_key = 6 + self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1] + if not self.options.batch: + self.win = win + self.resize() + try: + curses.use_default_colors() + curses.start_color() + curses.curs_set(0) + except curses.error: + # This call can fail with misconfigured terminals, for example + # TERM=xterm-color. This is harmless + pass + + def resize(self): + self.height, self.width = self.win.getmaxyx() + + def run(self): + iterations = 0 + poll = select.poll() + if not self.options.batch: + poll.register(sys.stdin.fileno(), select.POLLIN|select.POLLPRI) + while self.options.iterations is None or \ + iterations < self.options.iterations: + total, actual = self.process_list.refresh_processes() + self.refresh_display(iterations == 0, total, actual, + self.process_list.duration) + if self.options.iterations is not None: + iterations += 1 + if iterations >= self.options.iterations: + break + elif iterations == 0: + iterations = 1 + + try: + events = poll.poll(self.options.delay_seconds * 1000.0) + except select.error as e: + if e.args and e.args[0] == errno.EINTR: + events = [] + else: + raise + for (fd, event) in events: + if event & (select.POLLERR | select.POLLHUP): + sys.exit(1) + if not self.options.batch: + self.resize() + if events: + key = self.win.getch() + self.handle_key(key) + + def reverse_sorting(self): + self.sorting_reverse = not self.sorting_reverse + + def adjust_sorting_key(self, delta): + orig_sorting_key = self.sorting_key + self.sorting_key += delta + self.sorting_key = max(0, self.sorting_key) + self.sorting_key = min(len(IOTopUI.sorting_keys) - 1, self.sorting_key) + if orig_sorting_key != self.sorting_key: + self.sorting_reverse = IOTopUI.sorting_keys[self.sorting_key][1] + + # I wonder if switching to urwid for the display would be better here + + def prompt_str(self, prompt, default=None, empty_is_cancel=True): + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.addstr(1, 0, prompt, curses.A_BOLD) + self.win.refresh() + curses.echo() + curses.curs_set(1) + inp = self.win.getstr(1, len(prompt)) + curses.curs_set(0) + curses.noecho() + if inp not in (None, ''): + return inp + if empty_is_cancel: + raise CancelInput() + return default + + def prompt_int(self, prompt, default = None, empty_is_cancel = True): + inp = self.prompt_str(prompt, default, empty_is_cancel) + try: + return int(inp) + except ValueError: + raise InvalidInt() + + def prompt_pid(self): + try: + return self.prompt_int('PID to ionice: ') + except InvalidInt: + raise InvalidPid() + except CancelInput: + raise + + def prompt_tid(self): + try: + return self.prompt_int('TID to ionice: ') + except InvalidInt: + raise InvalidTid() + except CancelInput: + raise + + def prompt_data(self, ioprio_data): + try: + if ioprio_data is not None: + inp = self.prompt_int('I/O priority data (0-7, currently %s): ' + % ioprio_data, ioprio_data, False) + else: + inp = self.prompt_int('I/O priority data (0-7): ', None, False) + except InvalidInt: + raise InvalidIoprioData() + if inp < 0 or inp > 7: + raise InvalidIoprioData() + return inp + + def prompt_set(self, prompt, display_list, ret_list, selected): + try: + selected = ret_list.index(selected) + except ValueError: + selected = -1 + set_len = len(display_list) - 1 + while True: + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.insstr(1, 0, prompt, curses.A_BOLD) + offset = len(prompt) + for i, item in enumerate(display_list): + display = ' %s ' % item + if i is selected: + attr = curses.A_REVERSE + else: + attr = curses.A_NORMAL + self.win.insstr(1, offset, display, attr) + offset += len(display) + while True: + key = self.win.getch() + if key in (curses.KEY_LEFT, ord('l')) and selected > 0: + selected -= 1 + break + elif key in (curses.KEY_RIGHT, ord('r')) and selected < set_len: + selected += 1 + break + elif key in (curses.KEY_ENTER, ord('\n'), ord('\r')): + return ret_list[selected] + elif key in (27, curses.KEY_CANCEL, curses.KEY_CLOSE, + curses.KEY_EXIT, ord('q'), ord('Q')): + raise CancelInput() + + def prompt_class(self, ioprio_class=None): + prompt = 'I/O priority class: ' + classes_prompt = ['Real-time', 'Best-effort', 'Idle'] + classes_ret = ['rt', 'be', 'idle'] + if ioprio_class is None: + ioprio_class = 2 + inp = self.prompt_set(prompt, classes_prompt, classes_ret, ioprio_class) + return inp + + def prompt_error(self, error = 'Error!'): + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.insstr(1, 0, ' %s ' % error, curses.A_REVERSE) + self.win.refresh() + time.sleep(1) + + def prompt_clear(self): + self.win.hline(1, 0, ord(' ') | curses.A_NORMAL, self.width) + self.win.refresh() + + def handle_key(self, key): + def toggle_accumulated(): + self.options.accumulated ^= True + def toggle_only_io(): + self.options.only ^= True + def toggle_processes(): + self.options.processes ^= True + self.process_list.clear() + self.process_list.refresh_processes() + def ionice(): + try: + if self.options.processes: + pid = self.prompt_pid() + exec_unit = self.process_list.get_process(pid) + else: + tid = self.prompt_tid() + exec_unit = ThreadInfo(tid, + self.process_list.taskstats_connection) + ioprio_value = exec_unit.get_ioprio() + (ioprio_class, ioprio_data) = \ + ioprio.to_class_and_data(ioprio_value) + ioprio_class = self.prompt_class(ioprio_class) + if ioprio_class == 'idle': + ioprio_data = 0 + else: + ioprio_data = self.prompt_data(ioprio_data) + exec_unit.set_ioprio(ioprio_class, ioprio_data) + self.process_list.clear() + self.process_list.refresh_processes() + except IoprioSetError as e: + self.prompt_error('Error setting I/O priority: %s' % e.err) + except InvalidPid: + self.prompt_error('Invalid process id!') + except InvalidTid: + self.prompt_error('Invalid thread id!') + except InvalidIoprioData: + self.prompt_error('Invalid I/O priority data!') + except InvalidInt: + self.prompt_error('Invalid integer!') + except CancelInput: + self.prompt_clear() + else: + self.prompt_clear() + + key_bindings = { + ord('q'): + lambda: sys.exit(0), + ord('Q'): + lambda: sys.exit(0), + ord('r'): + lambda: self.reverse_sorting(), + ord('R'): + lambda: self.reverse_sorting(), + ord('a'): + toggle_accumulated, + ord('A'): + toggle_accumulated, + ord('o'): + toggle_only_io, + ord('O'): + toggle_only_io, + ord('p'): + toggle_processes, + ord('P'): + toggle_processes, + ord('i'): + ionice, + ord('I'): + ionice, + curses.KEY_LEFT: + lambda: self.adjust_sorting_key(-1), + curses.KEY_RIGHT: + lambda: self.adjust_sorting_key(1), + curses.KEY_HOME: + lambda: self.adjust_sorting_key(-len(IOTopUI.sorting_keys)), + curses.KEY_END: + lambda: self.adjust_sorting_key(len(IOTopUI.sorting_keys)) + } + + action = key_bindings.get(key, lambda: None) + action() + + def get_data(self): + def format(p): + stats = format_stats(self.options, p, self.process_list.duration) + io_delay, swapin_delay, read_bytes, write_bytes = stats + if Stats.has_blkio_delay_total: + delay_stats = '%7s %7s ' % (swapin_delay, io_delay) + else: + delay_stats = ' ?unavailable? ' + pid_format = '%%%dd' % MAX_PID_WIDTH + line = (pid_format + ' %4s %-8s %11s %11s %s') % ( + p.pid, p.get_ioprio(), p.get_user()[:8], read_bytes, + write_bytes, delay_stats) + cmdline = p.get_cmdline() + if not self.options.batch: + remaining_length = self.width - len(line) + if 2 < remaining_length < len(cmdline): + len1 = (remaining_length - 1) // 2 + offset2 = -(remaining_length - len1 - 1) + cmdline = cmdline[:len1] + '~' + cmdline[offset2:] + line += cmdline + if not self.options.batch: + line = line[:self.width] + return line + + def should_format(p): + return not self.options.only or \ + p.did_some_io(self.options.accumulated) + + processes = list(filter(should_format, + self.process_list.processes.values())) + key = IOTopUI.sorting_keys[self.sorting_key][0] + if self.options.accumulated: + stats_lambda = lambda p: p.stats_accum + else: + stats_lambda = lambda p: p.stats_delta + processes.sort(key=lambda p: key(p, stats_lambda(p)), + reverse=self.sorting_reverse) + if not self.options.batch: + del processes[self.height - 2:] + return list(map(format, processes)) + + def refresh_display(self, first_time, total, actual, duration): + summary = [ + 'Total DISK READ : %s | Total DISK WRITE : %s' % ( + format_bandwidth(self.options, total[0], duration).rjust(14), + format_bandwidth(self.options, total[1], duration).rjust(14)), + 'Actual DISK READ: %s | Actual DISK WRITE: %s' % ( + format_bandwidth(self.options, actual[0], duration).rjust(14), + format_bandwidth(self.options, actual[1], duration).rjust(14)) + ] + + pid = max(0, (MAX_PID_WIDTH - 3)) * ' ' + if self.options.processes: + pid += 'PID' + else: + pid += 'TID' + titles = [pid, ' PRIO', ' USER', ' DISK READ', ' DISK WRITE', + ' SWAPIN', ' IO', ' COMMAND'] + lines = self.get_data() + if self.options.time: + titles = [' TIME'] + titles + current_time = time.strftime('%H:%M:%S ') + lines = [current_time + l for l in lines] + summary = [current_time + s for s in summary] + if self.options.batch: + if self.options.quiet <= 2: + for s in summary: + print(s) + if self.options.quiet <= int(first_time): + print(''.join(titles)) + for l in lines: + print(l) + sys.stdout.flush() + else: + self.win.erase() + for i, s in enumerate(summary): + self.win.addstr(i, 0, s[:self.width]) + self.win.hline(len(summary), 0, ord(' ') | curses.A_REVERSE, + self.width) + remaining_cols = self.width + for i in range(len(titles)): + attr = curses.A_REVERSE + title = titles[i] + if i == self.sorting_key: + title = title[1:] + if i == self.sorting_key: + attr |= curses.A_BOLD + title += self.sorting_reverse and '>' or '<' + title = title[:remaining_cols] + remaining_cols -= len(title) + self.win.addstr(title, attr) + if Stats.has_blkio_delay_total: + status_msg = None + else: + status_msg = ('CONFIG_TASK_DELAY_ACCT not enabled in kernel, ' + 'cannot determine SWAPIN and IO %') + num_lines = min(len(lines), self.height - 2 - int(bool(status_msg))) + for i in range(num_lines): + try: + def print_line(line): + self.win.addstr(i + len(summary) + 1, 0, line) + try: + print_line(lines[i]) + except UnicodeEncodeError: + # Python2: 'ascii' codec can't encode character ... + # http://bugs.debian.org/708252 + print_line(lines[i].encode('utf-8')) + except curses.error: + pass + if status_msg: + self.win.insstr(self.height - len(summary), 0, status_msg, + curses.A_BOLD) + self.win.refresh() + +def run_iotop_window(win, options): + if options.batch: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + else: + def clean_exit(*args, **kwargs): + sys.exit(0) + signal.signal(signal.SIGINT, clean_exit) + signal.signal(signal.SIGTERM, clean_exit) + taskstats_connection = TaskStatsNetlink(options) + process_list = ProcessList(taskstats_connection, options) + ui = IOTopUI(win, process_list, options) + ui.run() + +def run_iotop(options): + try: + if options.batch: + return run_iotop_window(None, options) + else: + return curses.wrapper(run_iotop_window, options) + except OSError as e: + if e.errno == errno.EPERM: + print(e, file=sys.stderr) + print(''' +The Linux kernel interfaces that iotop relies on now require root priviliges +or the NET_ADMIN capability. This change occured because a security issue +(CVE-2011-2494) was found that allows leakage of sensitive data across user +boundaries. If you require the ability to run iotop as a non-root user, please +configure sudo to allow you to run iotop as root. + +Please do not file bugs on iotop about this.''', file=sys.stderr) + sys.exit(1) + else: + raise + +# +# Profiling +# + +def _profile(continuation): + prof_file = 'iotop.prof' + try: + import cProfile + import pstats + print('Profiling using cProfile') + cProfile.runctx('continuation()', globals(), locals(), prof_file) + stats = pstats.Stats(prof_file) + except ImportError: + import hotshot + import hotshot.stats + prof = hotshot.Profile(prof_file, lineevents=1) + print('Profiling using hotshot') + prof.runcall(continuation) + prof.close() + stats = hotshot.stats.load(prof_file) + stats.strip_dirs() + stats.sort_stats('time', 'calls') + stats.print_stats(50) + stats.print_callees(50) + os.remove(prof_file) + +# +# Main program +# + +USAGE = '''%s [OPTIONS] + +DISK READ and DISK WRITE are the block I/O bandwidth used during the sampling +period. SWAPIN and IO are the percentages of time the thread spent respectively +while swapping in and waiting on I/O more generally. PRIO is the I/O priority at +which the thread is running (set using the ionice command). + +Controls: left and right arrows to change the sorting column, r to invert the +sorting order, o to toggle the --only option, p to toggle the --processes +option, a to toggle the --accumulated option, i to change I/O priority, q to +quit, any other key to force a refresh.''' % sys.argv[0] + +def main(): + try: + locale.setlocale(locale.LC_ALL, '') + except locale.Error: + print('unable to set locale, falling back to the default locale') + parser = optparse.OptionParser(usage=USAGE, version='iotop ' + VERSION) + parser.add_option('-o', '--only', action='store_true', + dest='only', default=False, + help='only show processes or threads actually doing I/O') + parser.add_option('-b', '--batch', action='store_true', dest='batch', + help='non-interactive mode') + parser.add_option('-n', '--iter', type='int', dest='iterations', + metavar='NUM', + help='number of iterations before ending [infinite]') + parser.add_option('-d', '--delay', type='float', dest='delay_seconds', + help='delay between iterations [1 second]', + metavar='SEC', default=1) + parser.add_option('-p', '--pid', type='int', dest='pids', action='append', + help='processes/threads to monitor [all]', metavar='PID') + parser.add_option('-u', '--user', type='str', dest='users', action='append', + help='users to monitor [all]', metavar='USER') + parser.add_option('-P', '--processes', action='store_true', + dest='processes', default=False, + help='only show processes, not all threads') + parser.add_option('-a', '--accumulated', action='store_true', + dest='accumulated', default=False, + help='show accumulated I/O instead of bandwidth') + parser.add_option('-k', '--kilobytes', action='store_true', + dest='kilobytes', default=False, + help='use kilobytes instead of a human friendly unit') + parser.add_option('-t', '--time', action='store_true', dest='time', + help='add a timestamp on each line (implies --batch)') + parser.add_option('-q', '--quiet', action='count', dest='quiet', default=0, + help='suppress some lines of header (implies --batch)') + parser.add_option('--profile', action='store_true', dest='profile', + default=False, help=optparse.SUPPRESS_HELP) + + options, args = parser.parse_args() + if args: + parser.error('Unexpected arguments: ' + ' '.join(args)) + find_uids(options) + options.pids = options.pids or [] + options.batch = options.batch or options.time or options.quiet + + main_loop = lambda: run_iotop(options) + + if options.profile: + def safe_main_loop(): + try: + main_loop() + except: + pass + _profile(safe_main_loop) + else: + main_loop() + diff --git a/iotop/version.py b/iotop/version.py new file mode 100644 index 0000000..5f7dac7 --- /dev/null +++ b/iotop/version.py @@ -0,0 +1 @@ +VERSION = '0.6' diff --git a/iotop/vmstat.py b/iotop/vmstat.py new file mode 100644 index 0000000..4a99a86 --- /dev/null +++ b/iotop/vmstat.py @@ -0,0 +1,46 @@ +# 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 Library 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +# See the COPYING file for license information. +# +# Copyright (c) 2007 Guillaume Chazarain + +class VmStat(object): + def __init__(self): + self.vmstat_file = open('/proc/vmstat') + self.vmstat = self.read() + + def read(self): + def extract(line): + return int(line.split()[1]) * 1024 + + for line in self.vmstat_file: + if line.startswith('pgpgin '): + pgpgin = extract(line) + break + + for line in self.vmstat_file: + if line.startswith('pgpgout '): + pgpgout = extract(line) + break + + self.vmstat_file.seek(0) + return pgpgin, pgpgout + + def delta(self): + now = self.read() + delta = now[0] - self.vmstat[0], now[1] - self.vmstat[1] + self.vmstat = now + return delta + diff --git a/sbin/iotop b/sbin/iotop new file mode 100755 index 0000000..c5202d3 --- /dev/null +++ b/sbin/iotop @@ -0,0 +1,20 @@ +#!/usr/bin/python +# iotop: Display I/O usage of processes in a top like UI +# Copyright (c) 2007, 2008 Guillaume Chazarain , GPLv2 +# See iotop --help for some help + +from __future__ import print_function +import sys + +try: + from iotop.ui import main +except ImportError as e: + print(e) + print('To run an uninstalled copy of iotop,') + print('launch iotop.py in the top directory') +else: + try: + main() + except KeyboardInterrupt: + pass + sys.exit(0) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e617a4a --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[bdist_rpm] +doc_files = ChangeLog COPYING NEWS README THANKS +install_script = .install-rpm.sh diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..cd639ca --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +from distutils.core import setup +from distutils.command import install as distutils_install +from iotop.version import VERSION + +# Dirty hack to make setup.py install the iotop script to sbin/ instead of bin/ +# while still honoring the choice of installing into local/ or not. +if hasattr(distutils_install, 'INSTALL_SCHEMES'): + for d in distutils_install.INSTALL_SCHEMES.itervalues(): + if d.get('scripts', '').endswith('/bin'): + d['scripts'] = d['scripts'][:-len('/bin')] + '/sbin' + +setup(name='iotop', + version=VERSION, + description='Per process I/O bandwidth monitor', + long_description= +'''Iotop is a Python program with a top like UI used to show of behalf of which +process is the I/O going on.''', + author='Guillaume Chazarain', + author_email='guichaz@gmail.com', + url='http://guichaz.free.fr/iotop', + scripts=['sbin/iotop'], + data_files=[('share/man/man8', ['iotop.8'])], + packages=['iotop'], + license='GPL' +)