#!/usr/bin/python3 import os import json import traceback import time import sys import subprocess import logging msg = None def player_metadata(): for k in range(20): try: time.sleep(1.0) return subprocess.check_output(['playerctl', 'metadata']).decode('utf-8').strip() except: continue def memory_stats(): with io.BytesIO(subprocess.check_output('free', shell=True)) as f: t1 = f.read().decode('utf-8').splitlines() mem_total = int(t1[1].strip().split()[1]) mem_used = int(t1[1].strip().split()[2]) return dict( mem_total=mem_total, mem_used=mem_used, ) def eternal_oom(memory_limit=None): import signal import os import re import time import io import subprocess import pprint self_pid = os.getpid() if memory_limit is None: memory_limit = 3 * 1024 * 1024 assert isinstance(memory_limit, int) \ and memory_limit < memory_stats()['mem_total'] * 0.8 \ and memory_limit > 512 * 1024 def pandas_data_frame(lines, groups_regex, header_regex, extra_columns): header = re.compile(header_regex).search(lines[0]).groups() rows = [ re.compile(groups_regex).search(row).groups() for row in lines[1:] ] columns = { column: [] for column in header } for row in rows: for value, column in zip(row, header): columns[column].append(value) for column, transformation in extra_columns.items(): columns[column] = [ transformation( { k : v[index] for k, v in columns.items() } ) for index in range(len(rows)) ] return columns def pandas_merge(left, right, on): index = {} input_data_frames = [ ('left', left), ('right', right), ] for index_name, data_frame in input_data_frames: current_index = {} for row_index, value in enumerate(data_frame[on]): if not value in current_index: current_index[value] = [] current_index[value].append(row_index) index[index_name] = current_index merged_data_frame = dict( header=[ column + '_x' for column in left ] + [ column + '_y' for column in right ], columns={}, ) for column in merged_data_frame['header']: merged_data_frame['columns'][column] = [] common_values = { left_value for left_value in index['left'] if left_value in index['right'] } common_rows = sorted( [ dict( left_row_index=index['left'][value][0], right_row_index=index['right'][value][0], ) for value in common_values ], key=lambda x: x['left_row_index'], ) for common_row in common_rows: row = sum([ [ values[ common_row['%s_row_index' % index_name] ] for column, values in data_frame.items() ] for index_name, data_frame in input_data_frames ], []) for column, value in zip(merged_data_frame['header'], row): merged_data_frame['columns'][column].append(value) return merged_data_frame['columns'] def pandas_sort_values(data_frame, by, ascending): assert len(by) == 1 assert ascending is False t1 = [ o['row_index'] for o in sorted( [ dict( row_index=row_index, value=value ) for row_index, value in enumerate(data_frame[by[0]]) ], key=lambda x: x['value'] )[::-1] ] return { column : [ values[row_index] for row_index in t1 ] for column, values in data_frame.items() } def pandas_filter_values(data_frame, condition): shape = [ len(data_frame), ] if shape[0] > 0: shape.append( len(list(data_frame.values())[0]) ) t1 = [ row_index for row_index in range(shape[1]) if condition( { column : values[row_index] for column, values in data_frame.items() } ) ] return { column : [ values[row_index] for row_index in t1 ] for column, values in data_frame.items() } def pandas_row(data_frame, row_index): return { column : values[row_index] for column, values in data_frame.items() } while True: with io.BytesIO(subprocess.check_output('ps -e -o pid,rss,user', shell=True)) as f: t1 = pandas_data_frame( f.read().decode('utf-8').splitlines(), r'^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$', r'^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$', dict( PID=lambda row: int(row['PID']), RSS=lambda row: int(row['RSS']), ), ) mem_used = memory_stats()['mem_used'] t5 = subprocess.check_output('ps -e -o pid,args', shell=True).decode('utf-8').splitlines() t6 = pandas_data_frame( t5, r'^\s*(\d+)\s(.*)$', r'^\s+(\w+)\s+(\w+)\s*$', dict( PID=lambda row: int(row['PID']) ), ) t7 = pandas_merge(t1, t6, on='PID') t8 = pandas_sort_values(t7, by=['RSS_x'], ascending=False) t9 = pandas_filter_values( t8, lambda row: row['PID_x'] != self_pid and not 'freelancer' in row['COMMAND_y'] ) t4 = lambda : os.kill(t9['PID_x'][0], signal.SIGKILL) t10 = lambda : mem_used > memory_limit if t10(): pprint.pprint([ 'Killing', pandas_row(t9, 0), mem_used, ]) t4() time.sleep(1) def resilient_vlc(stream=None): if stream is None: streams_path = os.path.join( os.environ['CACHE_PATH'], 'resilient-vlc-streams.json' ) if os.path.exists(streams_path): with io.open( streams_path, 'r' ) as f: stream = json.load(f) else: raise RuntimeError( 'not found, %s' % streams_path ) if isinstance(stream, str): stream = [stream] if len(stream) == 0: raise RuntimeError('no streams') import subprocess import time while True: print('new start') with subprocess.Popen([ 'cvlc', '--verbose', '2', *stream, ], stderr=subprocess.PIPE) as p: while p.returncode is None: t1 = p.stderr.readline().decode('utf-8') if len(t1) > 0: print(t1) if not all([ o in t1 for o in [ 'prefetch stream error', 'terror', 'main interface error', ] ]) and any([ o in t1 for o in [ 'pulse audio output debug: underflow' ] ]): print('shit') p.kill() while True: try: t2 = p.wait(timeout=1) print(t2) break except: print('shit') pass time.sleep(1.0) def eternal_firefox( tabs=None, profile=None, group_name=None, window_position=None, debug=None, ): import os import datetime import pprint import subprocess import time if debug is None: debug = False if tabs is None: raise RuntimeError('no tabs provided') if profile is None: raise RuntimeError('no profile provided') if group_name is None: raise RuntimeError('no group provided') if window_position is None: #window_position = '1,600,0,600,540' raise RuntimeError('no window-position provided') while True: os.system(r'''date''') with subprocess.Popen([ 'firefox', '-P', profile, *tabs, ]) as p: try: if debug: assert subprocess.check_call(['notify-send', '%s:Starting' % group_name]) == 0 #t3 = '' for k in range(300): t1 = subprocess.check_output(r''' swaymsg -t get_tree | jq -r '..|try select(.pid== %d)' ''' % p.pid, shell=True).decode('utf-8') if len(t1) > 10: break #time.sleep(0.1) #t1 = subprocess.check_output(['wmctrl', '-p', '-l']).decode('utf-8') #t4 = [o for o in t1.splitlines() if str(p.pid) in o] #if len(t4) == 1: # t3 = t4[0] # break #if t3 == '': # raise RuntimeError #t2 = t3.split()[0] #assert os.system('wmctrl -i -r %s -e %s' % (t2, window_position)) == 0 #assert os.system('wmctrl -i -r %s -b add,below' % t2) == 0 def reposition(): assert os.system(r''' swaymsg '[pid="{{PID}}"] move absolute position {{X}}px {{Y}}px' && \ swaymsg '[pid="{{PID}}"] move window to workspace {{WORKSPACE}}' '''.replace('{{PID}}', str(p.pid)) \ .replace('{{X}}', str(window_position[1])) \ .replace('{{Y}}', str(window_position[2])) \ .replace('{{WORKSPACE}}', str(window_position[0]))) == 0 reposition() if False: for tab in tabs[1:]: time.sleep(10) assert subprocess.check_call([ 'firefox', '-P', profile, '--new-tab', tab, ]) == 0 if debug: assert subprocess.check_call(['notify-send', '%s:Started' % group_name]) == 0 start = datetime.datetime.now() is_to_restart = lambda : (datetime.datetime.now() - start).total_seconds() >= 900 * 4 polling_count = 0 while not is_to_restart(): if polling_count == 0: reposition() if not p.poll() is None: break time.sleep(10) polling_count += 1 if debug: assert subprocess.check_call(['notify-send', '%s:Closing' % group_name]) == 0 #assert os.system('wmctrl -i -c %s' % t2) == 0 assert os.system(r''' swaymsg '[pid="%d"] kill' ''' % (p.pid,)) == 0 except KeyboardInterrupt: assert os.system(r''' swaymsg '[pid="%d"] kill' ''' % (p.pid,)) == 0 break except: import traceback import pprint pprint.pprint(traceback.format_exc()) finally: try: p.wait(20) except subprocess.TimeoutExpired: pprint.pprint([p.pid, '20 seconds timeout', 'kill']) p.kill() if debug: assert subprocess.check_call(['notify-send', '%s:Closed' % group_name]) == 0 def resilient_ethernet(ip_addr, ethernet_device): subprocess.check_call( r''' sudo sh -c '\ while true; \ do ping -c 3 -w 3 -W 1 {{IP_ADDR}} || (\ ip link set {{ETHERNET_DEVICE}} down; \ ip link set {{ETHERNET_DEVICE}} up; \ sleep 4; true;\ ); \ sleep 10; clear; date; \ done' '''.replace( '{{IP_ADDR}}', ip_addr ).replace( '{{ETHERNET_DEVICE}}}', ethernet_device ), shell=True ) def status(): return ' | '.join([ subprocess.check_output(o, shell=True).decode('utf-8').strip() for o in [ r''' free -h | \ grep -P Mem: | grep -Po '[\w\.\d]+' | tail -n +2 | head -n 3 | xargs echo -n; ''', r''' sensors | \ grep -Po '[\\\+\\\-\\\w][^\\\s]+C ' | head -n 5 | xargs echo -n ''', r''' ssh nartes@pizcool3070 free -h | \ grep -P Mem: | grep -Po '[\w\.\d]+' | tail -n +2 | head -n 3 | xargs echo -n; ''', r''' ssh nartes@pizcool3070 sensors | \ grep -Po '[\\\+\\\-\.0-9]+\s+C ' | head -n 1 ''', r''' date +'%Y-%m-%d %l:%M:%S %p'; ''', ] ]).replace('\n\r', '') try: if sys.argv[1] == 'media-play-pause': subprocess.check_call(['playerctl', 'play-pause']) msg = player_metadata() elif sys.argv[1] == 'media-next': subprocess.check_call(['playerctl', 'next']) msg = player_metadata() elif sys.argv[1] == 'media-prev': subprocess.check_call(['playerctl', 'previous']) msg = player_metadata() elif sys.argv[1] == 'media-lower-volume': subprocess.check_call([ 'pactl', 'set-sink-volume', '@DEFAULT_SINK@', '-5%' ]) msg = subprocess.check_output([ 'pactl', 'get-sink-volume', '@DEFAULT_SINK@' ]).decode('utf-8').strip() elif sys.argv[1] == 'media-raise-volume': subprocess.check_call([ 'pactl', 'set-sink-volume', '@DEFAULT_SINK@', '+5%' ]) msg = subprocess.check_output([ 'pactl', 'get-sink-volume', '@DEFAULT_SINK@' ]).decode('utf-8').strip() elif sys.argv[1] == 'status': sys.stdout.write(status()) sys.stdout.flush() elif sys.argv[1] == 'http-server': subprocess.check_call(r''' sudo docker run \ -p 80:80 \ -u root \ -it --entrypoint=/bin/bash \ -v $PWD:/app:ro \ nginx:latest \ -c 'echo "server{listen 80; charset UTF-8; root /app; location / {autoindex on;}}" > /etc/nginx/conf.d/default.conf; nginx -g "daemon off;"' ''', shell=True) elif sys.argv[1] == 'wl-screenshot': subprocess.check_call(r''' grim -g "$(slurp)" - | wl-copy ''', shell=True) elif sys.argv[1] == 'eternal-oom': eternal_oom( memory_limit=json.loads(sys.argv[2]), ) elif sys.argv[1] == 'resilient-vlc': resilient_vlc(sys.argv[2:]) elif sys.argv[1] == 'eternal-firefox': eternal_firefox( profile=sys.argv[2], group_name=sys.argv[3], window_position=json.loads(sys.argv[4]), debug=json.loads(sys.argv[5]), tabs=sys.argv[6:], ) elif sys.argv[1] == 'resilient-ethernet': resilient_ethernet( ip_addr=sys.argv[2], ethernet_device=sys.argv[3], ) elif sys.argv[1] == 'desktop-services': assert all([ env_name in os.environ for env_name in [ 'GTK_IM_MODULE', 'XMODIFIERS', 'QT_IM_MODULE', 'I3SOCK', 'SWAYSOCK', 'WAYLAND_DISPLAY', ] ]) services = [] try: services.append( subprocess.Popen(['ibus-daemon']) ) for o in services: o.wait() finally: for o in services: try: o.terminate(timeout=10) except: logging.error('killed %s' % str(o.__dict__)) o.kill() else: raise NotImplementedError except: msg = 'not implemented\n%s' % traceback.format_exc() logging.error(msg) if not msg is None: subprocess.check_call([ 'notify-send', 'commands', msg[-128:] ])