iotop.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python
  2. # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """
  6. A clone of iotop (http://guichaz.free.fr/iotop/) showing real time
  7. disk I/O statistics.
  8. It works on Linux only (FreeBSD and OSX are missing support for IO
  9. counters).
  10. It doesn't work on Windows as curses module is required.
  11. Example output:
  12. $ python examples/iotop.py
  13. Total DISK READ: 0.00 B/s | Total DISK WRITE: 472.00 K/s
  14. PID USER DISK READ DISK WRITE COMMAND
  15. 13155 giampao 0.00 B/s 428.00 K/s /usr/bin/google-chrome-beta
  16. 3260 giampao 0.00 B/s 0.00 B/s bash
  17. 3779 giampao 0.00 B/s 0.00 B/s gnome-session --session=ubuntu
  18. 3830 giampao 0.00 B/s 0.00 B/s /usr/bin/dbus-launch
  19. 3831 giampao 0.00 B/s 0.00 B/s //bin/dbus-daemon --fork --print-pid 5
  20. 3841 giampao 0.00 B/s 0.00 B/s /usr/lib/at-spi-bus-launcher
  21. 3845 giampao 0.00 B/s 0.00 B/s /bin/dbus-daemon
  22. 3848 giampao 0.00 B/s 0.00 B/s /usr/lib/at-spi2-core/at-spi2-registryd
  23. 3862 giampao 0.00 B/s 0.00 B/s /usr/lib/gnome-settings-daemon
  24. Author: Giampaolo Rodola' <g.rodola@gmail.com>
  25. """
  26. import os
  27. import sys
  28. import psutil
  29. if not hasattr(psutil.Process, 'io_counters') or os.name != 'posix':
  30. sys.exit('platform not supported')
  31. import time
  32. import curses
  33. import atexit
  34. # --- curses stuff
  35. def tear_down():
  36. win.keypad(0)
  37. curses.nocbreak()
  38. curses.echo()
  39. curses.endwin()
  40. win = curses.initscr()
  41. atexit.register(tear_down)
  42. curses.endwin()
  43. lineno = 0
  44. def print_line(line, highlight=False):
  45. """A thin wrapper around curses's addstr()."""
  46. global lineno
  47. try:
  48. if highlight:
  49. line += " " * (win.getmaxyx()[1] - len(line))
  50. win.addstr(lineno, 0, line, curses.A_REVERSE)
  51. else:
  52. win.addstr(lineno, 0, line, 0)
  53. except curses.error:
  54. lineno = 0
  55. win.refresh()
  56. raise
  57. else:
  58. lineno += 1
  59. # --- /curses stuff
  60. def bytes2human(n):
  61. """
  62. >>> bytes2human(10000)
  63. '9.8 K/s'
  64. >>> bytes2human(100001221)
  65. '95.4 M/s'
  66. """
  67. symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
  68. prefix = {}
  69. for i, s in enumerate(symbols):
  70. prefix[s] = 1 << (i + 1) * 10
  71. for s in reversed(symbols):
  72. if n >= prefix[s]:
  73. value = float(n) / prefix[s]
  74. return '%.2f %s/s' % (value, s)
  75. return '%.2f B/s' % (n)
  76. def poll(interval):
  77. """Calculate IO usage by comparing IO statics before and
  78. after the interval.
  79. Return a tuple including all currently running processes
  80. sorted by IO activity and total disks I/O activity.
  81. """
  82. # first get a list of all processes and disk io counters
  83. procs = [p for p in psutil.process_iter()]
  84. for p in procs[:]:
  85. try:
  86. p._before = p.io_counters()
  87. except psutil.Error:
  88. procs.remove(p)
  89. continue
  90. disks_before = psutil.disk_io_counters()
  91. # sleep some time
  92. time.sleep(interval)
  93. # then retrieve the same info again
  94. for p in procs[:]:
  95. try:
  96. p._after = p.io_counters()
  97. p._cmdline = ' '.join(p.cmdline())
  98. if not p._cmdline:
  99. p._cmdline = p.name()
  100. p._username = p.username()
  101. except psutil.NoSuchProcess:
  102. procs.remove(p)
  103. disks_after = psutil.disk_io_counters()
  104. # finally calculate results by comparing data before and
  105. # after the interval
  106. for p in procs:
  107. p._read_per_sec = p._after.read_bytes - p._before.read_bytes
  108. p._write_per_sec = p._after.write_bytes - p._before.write_bytes
  109. p._total = p._read_per_sec + p._write_per_sec
  110. disks_read_per_sec = disks_after.read_bytes - disks_before.read_bytes
  111. disks_write_per_sec = disks_after.write_bytes - disks_before.write_bytes
  112. # sort processes by total disk IO so that the more intensive
  113. # ones get listed first
  114. processes = sorted(procs, key=lambda p: p._total, reverse=True)
  115. return (processes, disks_read_per_sec, disks_write_per_sec)
  116. def refresh_window(procs, disks_read, disks_write):
  117. """Print results on screen by using curses."""
  118. curses.endwin()
  119. templ = "%-5s %-7s %11s %11s %s"
  120. win.erase()
  121. disks_tot = "Total DISK READ: %s | Total DISK WRITE: %s" \
  122. % (bytes2human(disks_read), bytes2human(disks_write))
  123. print_line(disks_tot)
  124. header = templ % ("PID", "USER", "DISK READ", "DISK WRITE", "COMMAND")
  125. print_line(header, highlight=True)
  126. for p in procs:
  127. line = templ % (
  128. p.pid,
  129. p._username[:7],
  130. bytes2human(p._read_per_sec),
  131. bytes2human(p._write_per_sec),
  132. p._cmdline)
  133. try:
  134. print_line(line)
  135. except curses.error:
  136. break
  137. win.refresh()
  138. def main():
  139. try:
  140. interval = 0
  141. while 1:
  142. args = poll(interval)
  143. refresh_window(*args)
  144. interval = 1
  145. except (KeyboardInterrupt, SystemExit):
  146. pass
  147. if __name__ == '__main__':
  148. main()