|
1 #!/usr/bin/env python |
|
2 |
|
3 # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. |
|
4 # Use of this source code is governed by a BSD-style license that can be |
|
5 # found in the LICENSE file. |
|
6 |
|
7 """ |
|
8 A clone of top / htop. |
|
9 |
|
10 Author: Giampaolo Rodola' <g.rodola@gmail.com> |
|
11 """ |
|
12 |
|
13 import os |
|
14 import sys |
|
15 if os.name != 'posix': |
|
16 sys.exit('platform not supported') |
|
17 import time |
|
18 import curses |
|
19 import atexit |
|
20 from datetime import datetime, timedelta |
|
21 |
|
22 import psutil |
|
23 |
|
24 |
|
25 # --- curses stuff |
|
26 def tear_down(): |
|
27 win.keypad(0) |
|
28 curses.nocbreak() |
|
29 curses.echo() |
|
30 curses.endwin() |
|
31 |
|
32 win = curses.initscr() |
|
33 atexit.register(tear_down) |
|
34 curses.endwin() |
|
35 lineno = 0 |
|
36 |
|
37 def print_line(line, highlight=False): |
|
38 """A thin wrapper around curses's addstr().""" |
|
39 global lineno |
|
40 try: |
|
41 if highlight: |
|
42 line += " " * (win.getmaxyx()[1] - len(line)) |
|
43 win.addstr(lineno, 0, line, curses.A_REVERSE) |
|
44 else: |
|
45 win.addstr(lineno, 0, line, 0) |
|
46 except curses.error: |
|
47 lineno = 0 |
|
48 win.refresh() |
|
49 raise |
|
50 else: |
|
51 lineno += 1 |
|
52 # --- /curses stuff |
|
53 |
|
54 |
|
55 def bytes2human(n): |
|
56 """ |
|
57 >>> bytes2human(10000) |
|
58 '9K' |
|
59 >>> bytes2human(100001221) |
|
60 '95M' |
|
61 """ |
|
62 symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y') |
|
63 prefix = {} |
|
64 for i, s in enumerate(symbols): |
|
65 prefix[s] = 1 << (i+1)*10 |
|
66 for s in reversed(symbols): |
|
67 if n >= prefix[s]: |
|
68 value = int(float(n) / prefix[s]) |
|
69 return '%s%s' % (value, s) |
|
70 return "%sB" % n |
|
71 |
|
72 def poll(interval): |
|
73 # sleep some time |
|
74 time.sleep(interval) |
|
75 procs = [] |
|
76 procs_status = {} |
|
77 for p in psutil.process_iter(): |
|
78 try: |
|
79 p.dict = p.as_dict(['username', 'get_nice', 'get_memory_info', |
|
80 'get_memory_percent', 'get_cpu_percent', |
|
81 'get_cpu_times', 'name', 'status']) |
|
82 try: |
|
83 procs_status[str(p.dict['status'])] += 1 |
|
84 except KeyError: |
|
85 procs_status[str(p.dict['status'])] = 1 |
|
86 except psutil.NoSuchProcess: |
|
87 pass |
|
88 else: |
|
89 procs.append(p) |
|
90 |
|
91 # return processes sorted by CPU percent usage |
|
92 processes = sorted(procs, key=lambda p: p.dict['cpu_percent'], reverse=True) |
|
93 return (processes, procs_status) |
|
94 |
|
95 def print_header(procs_status, num_procs): |
|
96 """Print system-related info, above the process list.""" |
|
97 |
|
98 def get_dashes(perc): |
|
99 dashes = "|" * int((float(perc) / 10 * 4)) |
|
100 empty_dashes = " " * (40 - len(dashes)) |
|
101 return dashes, empty_dashes |
|
102 |
|
103 # cpu usage |
|
104 for cpu_num, perc in enumerate(psutil.cpu_percent(interval=0, percpu=True)): |
|
105 dashes, empty_dashes = get_dashes(perc) |
|
106 print_line(" CPU%-2s [%s%s] %5s%%" % (cpu_num, dashes, empty_dashes, |
|
107 perc)) |
|
108 mem = psutil.virtual_memory() |
|
109 dashes, empty_dashes = get_dashes(mem.percent) |
|
110 used = mem.total - mem.available |
|
111 line = " Mem [%s%s] %5s%% %6s/%s" % ( |
|
112 dashes, empty_dashes, |
|
113 mem.percent, |
|
114 str(int(used / 1024 / 1024)) + "M", |
|
115 str(int(mem.total / 1024 / 1024)) + "M" |
|
116 ) |
|
117 print_line(line) |
|
118 |
|
119 # swap usage |
|
120 swap = psutil.swap_memory() |
|
121 dashes, empty_dashes = get_dashes(swap.percent) |
|
122 line = " Swap [%s%s] %5s%% %6s/%s" % ( |
|
123 dashes, empty_dashes, |
|
124 swap.percent, |
|
125 str(int(swap.used / 1024 / 1024)) + "M", |
|
126 str(int(swap.total / 1024 / 1024)) + "M" |
|
127 ) |
|
128 print_line(line) |
|
129 |
|
130 # processes number and status |
|
131 st = [] |
|
132 for x, y in procs_status.items(): |
|
133 if y: |
|
134 st.append("%s=%s" % (x, y)) |
|
135 st.sort(key=lambda x: x[:3] in ('run', 'sle'), reverse=1) |
|
136 print_line(" Processes: %s (%s)" % (num_procs, ' '.join(st))) |
|
137 # load average, uptime |
|
138 uptime = datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME) |
|
139 av1, av2, av3 = os.getloadavg() |
|
140 line = " Load average: %.2f %.2f %.2f Uptime: %s" \ |
|
141 % (av1, av2, av3, str(uptime).split('.')[0]) |
|
142 print_line(line) |
|
143 |
|
144 def refresh_window(procs, procs_status): |
|
145 """Print results on screen by using curses.""" |
|
146 curses.endwin() |
|
147 templ = "%-6s %-8s %4s %5s %5s %6s %4s %9s %2s" |
|
148 win.erase() |
|
149 header = templ % ("PID", "USER", "NI", "VIRT", "RES", "CPU%", "MEM%", |
|
150 "TIME+", "NAME") |
|
151 print_header(procs_status, len(procs)) |
|
152 print_line("") |
|
153 print_line(header, highlight=True) |
|
154 for p in procs: |
|
155 # TIME+ column shows process CPU cumulative time and it |
|
156 # is expressed as: "mm:ss.ms" |
|
157 if p.dict['cpu_times'] != None: |
|
158 ctime = timedelta(seconds=sum(p.dict['cpu_times'])) |
|
159 ctime = "%s:%s.%s" % (ctime.seconds // 60 % 60, |
|
160 str((ctime.seconds % 60)).zfill(2), |
|
161 str(ctime.microseconds)[:2]) |
|
162 else: |
|
163 ctime = '' |
|
164 if p.dict['memory_percent'] is not None: |
|
165 p.dict['memory_percent'] = round(p.dict['memory_percent'], 1) |
|
166 else: |
|
167 p.dict['memory_percent'] = '' |
|
168 if p.dict['cpu_percent'] is None: |
|
169 p.dict['cpu_percent'] = '' |
|
170 if p.dict['username']: |
|
171 username = p.dict['username'][:8] |
|
172 else: |
|
173 username = "" |
|
174 line = templ % (p.pid, |
|
175 username, |
|
176 p.dict['nice'], |
|
177 bytes2human(getattr(p.dict['memory_info'], 'vms', 0)), |
|
178 bytes2human(getattr(p.dict['memory_info'], 'rss', 0)), |
|
179 p.dict['cpu_percent'], |
|
180 p.dict['memory_percent'], |
|
181 ctime, |
|
182 p.dict['name'] or '', |
|
183 ) |
|
184 try: |
|
185 print_line(line) |
|
186 except curses.error: |
|
187 break |
|
188 win.refresh() |
|
189 |
|
190 |
|
191 def main(): |
|
192 try: |
|
193 interval = 0 |
|
194 while 1: |
|
195 args = poll(interval) |
|
196 refresh_window(*args) |
|
197 interval = 1 |
|
198 except (KeyboardInterrupt, SystemExit): |
|
199 pass |
|
200 |
|
201 if __name__ == '__main__': |
|
202 main() |