cpu_mon.py - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) 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 delta t.
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 yours of ~/.config/procps)
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 /proc/stat
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 command
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 # ------------------------------------------------------------------------------ config
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_max_lines)
49 Sensors = ['cpu']
50 # Sensors = ['time']
51 bars = ' ▁▂▃▄▅▆▇'
52 # -------------------------------------------------------------------------- end config
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': None}
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 there is activity
129 if h[0] > th_cpu_min_to_show_procs:
130 add_top_cpu_eaters(r, show_max_procs, h) # for status bar
131 ctx['col_cpu'] = col_high if h[0] > th_color_high_cpu else col_norm
132 v = lambda d: '' if d < th_min_cpu_show_core else bars[int(d / bar_intv)]
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 from XFCE4:
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_old_ts'])) + t
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 binary)
223 top_cfg = '''
224 dG9wJ3MgQ29uZmlnIEZpbGUgKExpbnV4IHByb2Nlc3NlcyB3aXRoIHdpbmRvd3MpCklkOmosIE1v
225 ZGVfYWx0c2NyPTAsIE1vZGVfaXJpeHBzPTEsIERlbGF5X3RpbWU9My4wLCBDdXJ3aW49MApEZWYJ
226 ZmllbGRzY3VyPaWmqDO0Oz1AxLe6OcUnKSorLC0uLzAxMjU2ODw+P0FCQ0ZHSElKS0xNTk9QUVJT
227 VFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6Cgl3aW5mbGFncz0xNjEwNzYs
228 IHNvcnRpbmR4PTE4LCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAsIGdyYXBoX21lbXM9MCwgZG91
229 YmxlX3VwPTAsIGNvbWJpbmVfY3B1cz0wCglzdW1tY2xyPTEsIG1zZ3NjbHI9MSwgaGVhZGNscj0z
230 LCB0YXNrY2xyPTEKSm9iCWZpZWxkc2N1cj2lprm3uiiztMS7vUA8p8UpKissLS4vMDEyNTY4Pj9B
231 QkNGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5egoJ
232 d2luZmxhZ3M9MTkzODQ0LCBzb3J0aW5keD0wLCBtYXh0YXNrcz0wLCBncmFwaF9jcHVzPTAsIGdy
233 YXBoX21lbXM9MCwgZG91YmxlX3VwPTAsIGNvbWJpbmVfY3B1cz0wCglzdW1tY2xyPTYsIG1zZ3Nj
234 bHI9NiwgaGVhZGNscj03LCB0YXNrY2xyPTYKTWVtCWZpZWxkc2N1cj2lurs8vb6/wMFNQk7DRDM0
235 t8UmJygpKissLS4vMDEyNTY4OUZHSElKS0xPUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xt
236 bm9wcXJzdHV2d3h5egoJd2luZmxhZ3M9MTkzODQ0LCBzb3J0aW5keD0yMSwgbWF4dGFza3M9MCwg
237 Z3JhcGhfY3B1cz0wLCBncmFwaF9tZW1zPTAsIGRvdWJsZV91cD0wLCBjb21iaW5lX2NwdXM9MAoJ
238 c3VtbWNscj01LCBtc2dzY2xyPTUsIGhlYWRjbHI9NCwgdGFza2Nscj01ClVzcglmaWVsZHNjdXI9
239 paanqKqwube6xMUpKywtLi8xMjM0NTY4Ozw9Pj9AQUJDRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xd
240 Xl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoKCXdpbmZsYWdzPTE5Mzg0NCwgc29ydGluZHg9
241 MywgbWF4dGFza3M9MCwgZ3JhcGhfY3B1cz0wLCBncmFwaF9tZW1zPTAsIGRvdWJsZV91cD0wLCBj
242 b21iaW5lX2NwdXM9MAoJc3VtbWNscj0zLCBtc2dzY2xyPTMsIGhlYWRjbHI9MiwgdGFza2Nscj0z
243 CkZpeGVkX3dpZGVzdD0wLCBTdW1tX21zY2FsZT0xLCBUYXNrX21zY2FsZT0wLCBaZXJvX3N1cHBy
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()