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 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()