cpu_mon.py - sites - public wiki contents of suckless.org | |
git clone git://git.suckless.org/sites | |
Log | |
Files | |
Refs | |
--- | |
cpu_mon.py (8598B) | |
--- | |
1 #!/usr/bin/python | |
2 # coding: utf-8 | |
3 """ | |
4 GENMON APPLET USAGE EXAMPLE | |
5 | |
6 A Custom CPU monitor. | |
7 | |
8 Challenge: We want a bit of history and cpu monitoring anyway needs some… | |
9 | |
10 So we run this as daemon. | |
11 It keeps writing cpu infos to file -> start me in autostart.sh | |
12 | |
13 Hightlights: | |
14 - cpu per core | |
15 - top cpu eating process, customized with own config (not colliding with… | |
16 - arbitray long symbol lists, will pick per percent | |
17 - output colorized using pango | |
18 | |
19 Lowlights: | |
20 Linux Only. Not hard to rewrite, but now it's just Linux, looking into /… | |
21 For BSD check the suckless' slstatus regarding how to derive load. | |
22 | |
23 Usage: | |
24 Start this tool e.g. in autostart.sh and leave running. | |
25 In genmon use `cat $HOME/.dwm/out.cpu_mon` for the single shot tray icon… | |
26 """ | |
27 | |
28 import os, sys, psutil, time, subprocess as sp | |
29 import time | |
30 | |
31 here = os.path.abspath((os.path.dirname(__file__))) | |
32 | |
33 # ----------------------------------------------------------------------… | |
34 col_norm = '#a093c7' | |
35 col_high = '#bf568b' | |
36 | |
37 # we run top whenever a core if over this: | |
38 th_cpu_min_to_snapshot_top = 20 | |
39 # we show proc names whenever its utilizaition is over this: | |
40 th_cpu_min_to_show_procs = 80 | |
41 # 0: show always (>0: no space taken for core) | |
42 th_min_cpu_show_core = 0 | |
43 show_max_procs = 3 | |
44 th_color_high_cpu = 80 | |
45 top_output_max_lines = 20 | |
46 | |
47 top_rc_dir = here + '/.config/procps' | |
48 top = 'HOME="%s" top -b -1 -n 1 -w 56 | head -n %s' % (here, top_output_… | |
49 Sensors = ['cpu'] | |
50 # Sensors = ['time'] | |
51 bars = ' ▁▂▃▄▅▆▇' | |
52 # ----------------------------------------------------------------------… | |
53 | |
54 | |
55 # configure the panel item to cat this file: | |
56 fn_out = here + '/out.cpu_mon' | |
57 | |
58 | |
59 # maybe we want arrows stuff some day: | |
60 Traffic100 = 1024 # bytes | |
61 # arr_downs = ' 🢓↓⬇ﰬ🡇' | |
62 arr_downs = ' ↓⬇ﰬ🡇' | |
63 arr_ups = ' ↑⬆🡅' | |
64 | |
65 s = [] | |
66 CPUs = psutil.cpu_count() | |
67 # normal way to read load: read /proc/stat | |
68 ctx = {'proc_stat': [0 for i in range(CPUs)], 'traffic': [0, 0], 'fifo':… | |
69 | |
70 | |
71 bar_intv = 100.0 / len(bars) | |
72 arr_downs_intv = 100.0 / len(arr_downs) | |
73 arr_ups_intv = 100.0 / len(arr_ups) | |
74 arrows = [[arr_downs, arr_downs_intv], [arr_ups, arr_ups_intv]] | |
75 | |
76 # delivers the *cummulated* load values - per cpu. | |
77 # A difference of 100 within 1 sec means: fully loaded | |
78 proc_stat = '/proc/stat' | |
79 | |
80 | |
81 run_top = lambda: os.popen(top).read() | |
82 | |
83 | |
84 def cmd_colmn(): | |
85 # cache the position of the COMMAND column, we need it all the time | |
86 n = 'cpu_top_cmd_col' | |
87 c = ctx.get(n) | |
88 if not c: | |
89 t = ctx['cpu_top'] | |
90 c = ctx[n] = len(t.split(' COMMAND', 1)[0].rsplit('\n', 1)[1]) | |
91 return c | |
92 | |
93 | |
94 def add_top_cpu_eaters(r, count, cpu): | |
95 """Get the <count> top most cpu eating procs names as shown by top""" | |
96 # TODO: import re would not hurt | |
97 t = ctx['cpu_top'] | |
98 p = t.split('COMMAND', 1)[1].split('\n', 1 + count) | |
99 colmn = cmd_colmn() | |
100 for i in range(count, 0, -1): | |
101 if cpu[i - 1] < th_cpu_min_to_show_procs: | |
102 continue | |
103 # P = p)[nr].split()[11:]) | |
104 r.insert(0, '%s ' % p[i][colmn:].replace(' ', '')[:10]) | |
105 | |
106 | |
107 class sensors: | |
108 def cpu(): | |
109 r = [] | |
110 l = ctx.pop('cpu_top', 0) | |
111 if l: | |
112 ctx['cpu_top_old'] = l | |
113 ctx['cpu_top_old_ts'] = time.time() | |
114 with open(proc_stat) as fd: | |
115 t = fd.read() | |
116 o = ctx['proc_stat'] | |
117 h = [] | |
118 for i in range(CPUs): | |
119 v, t = t.split('cpu%s ' % i, 1)[1].split('\n', 1) | |
120 v = int(v.split(' ', 1)[0]) | |
121 d = min(v - o[i], 99.9) | |
122 o[i] = v | |
123 # print(i, d, file=sys.stderr) | |
124 h.append(d) | |
125 h = list(reversed(sorted(h))) | |
126 # show top process: | |
127 if h[0] > th_cpu_min_to_snapshot_top: # 20 | |
128 ctx['cpu_top'] = run_top() # for hover tip - only when ther… | |
129 if h[0] > th_cpu_min_to_show_procs: | |
130 add_top_cpu_eaters(r, show_max_procs, h) # for status b… | |
131 ctx['col_cpu'] = col_high if h[0] > th_color_high_cpu else col_n… | |
132 v = lambda d: '' if d < th_min_cpu_show_core else bars[int(d / b… | |
133 [r.append(v(d)) for d in h] | |
134 return ''.join(r) | |
135 | |
136 | |
137 # These would be other sensors - but for those we take the original ones… | |
138 # def time(): | |
139 # t = time.ctime().split() | |
140 # t.pop(1) # month | |
141 # t.pop() | |
142 # return ' '.join(t) | |
143 | |
144 # def mem(): | |
145 # return '%s' % psutil.virtual_memory().percent | |
146 | |
147 # def traffic(): | |
148 # r = [] | |
149 # o = ctx['traffic'] | |
150 # h = psutil.net_io_counters(pernic=False) | |
151 # v = [h.bytes_sent, h.bytes_recv] | |
152 # print('') | |
153 # for i in range(2): | |
154 # d = 100 * (min((v[i] - o[i]), Traffic100 - 1) / Traffic100) | |
155 # # print('%s\t%s' % (v[i] - o[i], d)) | |
156 # o[i] = v[i] | |
157 # arrs, arr_int = arrows[i] | |
158 # col = '\x04' if i == 0 else '\x03' | |
159 # s = arrs[int(d / arr_int)] | |
160 # r.append('%s%s' % (col, s)) | |
161 # return ''.join(r) | |
162 | |
163 # def battery(): | |
164 # B = '' | |
165 # P = 'ﮤ' | |
166 # d = psutil.sensors_battery() | |
167 # d, pp = int(d.percent), d.power_plugged | |
168 # p = '\x02' + P[0] if pp else '\x04' + P[1] | |
169 # s = B[int(min(d, 99) / (100 / len(B)))] | |
170 # if d < 30: | |
171 # s = '\x04' + s | |
172 # if d < 60: | |
173 # s = '\x03' + s | |
174 # else: | |
175 # s = '\x02' + s | |
176 # if d > 90 and pp: | |
177 # return '' | |
178 # return s + ' ' + p + ' ' | |
179 | |
180 | |
181 # for dwm's status bar (old version, caused high cpu): | |
182 # def xsetroot(sl): | |
183 # if os.system('xsetroot -name "%s"' % sl): | |
184 # print('exitting status.py') | |
185 # sys.exit(1) | |
186 | |
187 | |
188 def to_stdout(sl): | |
189 sl = '<txt><span fgcolor="%s">%s</span></txt>' % (ctx['col_cpu'], sl) | |
190 t = ctx.get('cpu_top') | |
191 if not t: | |
192 t = ctx.get('cpu_top_old') | |
193 if t: | |
194 t = '%s Seconds Ago:\n' % (int(time.time() - ctx['cpu_top_ol… | |
195 | |
196 if t: | |
197 sl += '<tool><span font_family="monospace">%s</span></tool>' % t | |
198 print(sl) | |
199 fd = ctx['fd_out'] | |
200 fd.seek(0) | |
201 fd.write(sl) | |
202 fd.flush() | |
203 | |
204 | |
205 def main(): | |
206 ctx['fd_out'] = open(fn_out, 'w') | |
207 out = to_stdout | |
208 while True: | |
209 s.clear() | |
210 for w in Sensors: | |
211 k = getattr(sensors, w)() | |
212 s.append('%s ' % k) | |
213 sl = ''.join(s) | |
214 r = os.popen('ls -lta --color=always').read() | |
215 out(sl) | |
216 time.sleep(1) # other values: load calc must be adapted. | |
217 | |
218 | |
219 # Follows the top config - you cant use CLI flags for this. | |
220 # Created by: `HOME=~/.dwm top` -> F (select fields) -> W (write toprc) | |
221 # then: | |
222 # cat .dwm/.config/procps/toprc | base64 >> $HOME/bin/cpu_mon.py (is bi… | |
223 top_cfg = ''' | |
224 dG9wJ3MgQ29uZmlnIEZpbGUgKExpbnV4IHByb2Nlc3NlcyB3aXRoIHdpbmRvd3MpCklkOmos… | |
225 ZGVfYWx0c2NyPTAsIE1vZGVfaXJpeHBzPTEsIERlbGF5X3RpbWU9My4wLCBDdXJ3aW49MApE… | |
226 ZmllbGRzY3VyPaWmqDO0Oz1AxLe6OcUnKSorLC0uLzAxMjU2ODw+P0FCQ0ZHSElKS0xNTk9Q… | |
227 VFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6Cgl3aW5mbGFncz0xNjEw… | |
228 IHNvcnRpbmR4PTE4LCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAsIGdyYXBoX21lbXM9MCwg… | |
229 YmxlX3VwPTAsIGNvbWJpbmVfY3B1cz0wCglzdW1tY2xyPTEsIG1zZ3NjbHI9MSwgaGVhZGNs… | |
230 LCB0YXNrY2xyPTEKSm9iCWZpZWxkc2N1cj2lprm3uiiztMS7vUA8p8UpKissLS4vMDEyNTY4… | |
231 QkNGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5… | |
232 d2luZmxhZ3M9MTkzODQ0LCBzb3J0aW5keD0wLCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAs… | |
233 YXBoX21lbXM9MCwgZG91YmxlX3VwPTAsIGNvbWJpbmVfY3B1cz0wCglzdW1tY2xyPTYsIG1z… | |
234 bHI9NiwgaGVhZGNscj03LCB0YXNrY2xyPTYKTWVtCWZpZWxkc2N1cj2lurs8vb6/wMFNQk7D… | |
235 t8UmJygpKissLS4vMDEyNTY4OUZHSElKS0xPUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlq… | |
236 bm9wcXJzdHV2d3h5egoJd2luZmxhZ3M9MTkzODQ0LCBzb3J0aW5keD0yMSwgbWF4dGFza3M9… | |
237 Z3JhcGhfY3B1cz0wLCBncmFwaF9tZW1zPTAsIGRvdWJsZV91cD0wLCBjb21iaW5lX2NwdXM9… | |
238 c3VtbWNscj01LCBtc2dzY2xyPTUsIGhlYWRjbHI9NCwgdGFza2Nscj01ClVzcglmaWVsZHNj… | |
239 paanqKqwube6xMUpKywtLi8xMjM0NTY4Ozw9Pj9AQUJDRkdISUpLTE1OT1BRUlNUVVZXWFla… | |
240 Xl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoKCXdpbmZsYWdzPTE5Mzg0NCwgc29ydGlu… | |
241 MywgbWF4dGFza3M9MCwgZ3JhcGhfY3B1cz0wLCBncmFwaF9tZW1zPTAsIGRvdWJsZV91cD0w… | |
242 b21iaW5lX2NwdXM9MAoJc3VtbWNscj0zLCBtc2dzY2xyPTMsIGhlYWRjbHI9MiwgdGFza2Ns… | |
243 CkZpeGVkX3dpZGVzdD0wLCBTdW1tX21zY2FsZT0xLCBUYXNrX21zY2FsZT0wLCBaZXJvX3N1… | |
244 ZXNzPTAK | |
245 '''.strip() | |
246 | |
247 | |
248 import base64 | |
249 | |
250 | |
251 def write_top_cfg(): | |
252 os.makedirs(top_rc_dir, exist_ok=True) | |
253 with open(top_rc_dir + '/toprc', 'wb') as fd: | |
254 fd.write(base64.standard_b64decode(top_cfg)) | |
255 | |
256 | |
257 if __name__ == '__main__': | |
258 write_top_cfg() | |
259 try: | |
260 main() | |
261 finally: | |
262 ctx['fd_out'].close() |