[~] Refactor
This commit is contained in:
parent
3dddb9f670
commit
da1fb2303f
@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
import sys
|
import sys
|
||||||
@ -16,7 +17,40 @@ import io
|
|||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
msg = None
|
def custom_notify(
|
||||||
|
title=None,
|
||||||
|
msg=None
|
||||||
|
):
|
||||||
|
if title is None:
|
||||||
|
title = 'commands'
|
||||||
|
|
||||||
|
assert isinstance(title, str) and len(title) > 0
|
||||||
|
assert isinstance(msg, str) and len(msg) > 0
|
||||||
|
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
osascript_translate = functools.partial(
|
||||||
|
custom_translate,
|
||||||
|
check=lambda a, b:
|
||||||
|
not re.compile(
|
||||||
|
r'^[a-zA-Z0-9\<\>\/\(\)\s\.\,\:]*$'
|
||||||
|
)\
|
||||||
|
.match(b) is None,
|
||||||
|
)
|
||||||
|
|
||||||
|
subprocess.check_call([
|
||||||
|
'osascript',
|
||||||
|
'-e',
|
||||||
|
'display notification "%s" with title "%s"' % (
|
||||||
|
osascript_translate(msg),
|
||||||
|
osascript_translate(title),
|
||||||
|
)
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
subprocess.check_call([
|
||||||
|
'notify-send',
|
||||||
|
title,
|
||||||
|
msg[-128:]
|
||||||
|
])
|
||||||
|
|
||||||
def intercept_output(
|
def intercept_output(
|
||||||
current_subprocess,
|
current_subprocess,
|
||||||
@ -135,6 +169,12 @@ def eternal_oom(argv):
|
|||||||
default=None,
|
default=None,
|
||||||
type=int,
|
type=int,
|
||||||
)
|
)
|
||||||
|
parser.add_option(
|
||||||
|
'--cpu_limit',
|
||||||
|
dest='cpu_limit',
|
||||||
|
default=None,
|
||||||
|
type=float,
|
||||||
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--debug',
|
'--debug',
|
||||||
dest='debug',
|
dest='debug',
|
||||||
@ -147,10 +187,17 @@ def eternal_oom(argv):
|
|||||||
|
|
||||||
if options.memory_limit is None:
|
if options.memory_limit is None:
|
||||||
options.memory_limit = 3 * 1024 * 1024
|
options.memory_limit = 3 * 1024 * 1024
|
||||||
|
if options.cpu_limit is None:
|
||||||
|
options.cpu_limit = 0.6 * os.cpu_count()
|
||||||
|
|
||||||
assert isinstance(options.memory_limit, int) \
|
assert isinstance(options.memory_limit, int) \
|
||||||
and options.memory_limit < memory_stats()['mem_total'] * 0.8 \
|
and options.memory_limit < memory_stats()['mem_total'] * 0.8 \
|
||||||
and options.memory_limit > 512 * 1024
|
and options.memory_limit > 512 * 1024
|
||||||
|
|
||||||
|
assert isinstance(options.cpu_limit, float) \
|
||||||
|
and options.cpu_limit > 0.2 * os.cpu_count() and \
|
||||||
|
options.cpu_limit < os.cpu_count() * 0.8
|
||||||
|
|
||||||
def pandas_data_frame(lines, groups_regex, header_regex, extra_columns):
|
def pandas_data_frame(lines, groups_regex, header_regex, extra_columns):
|
||||||
header = re.compile(header_regex).search(lines[0]).groups()
|
header = re.compile(header_regex).search(lines[0]).groups()
|
||||||
rows = [
|
rows = [
|
||||||
@ -306,23 +353,33 @@ def eternal_oom(argv):
|
|||||||
rows_count,
|
rows_count,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def ps_regex(groups_cnt):
|
||||||
|
assert groups_cnt >= 1
|
||||||
|
return ''.join([
|
||||||
|
r'^\s*',
|
||||||
|
r'([^\s]+)\s+' * (groups_cnt - 1),
|
||||||
|
r'([^\s]+)\s*$',
|
||||||
|
])
|
||||||
|
|
||||||
def oom_get_processes():
|
def oom_get_processes():
|
||||||
with io.BytesIO(
|
with io.BytesIO(
|
||||||
subprocess.check_output(
|
subprocess.check_output(
|
||||||
'ps -e -o pid,rss,user',
|
'ps -e -o pid,rss,user,%cpu',
|
||||||
shell=True
|
shell=True
|
||||||
)
|
)
|
||||||
) as f:
|
) as f:
|
||||||
t1 = pandas_data_frame(
|
t1 = pandas_data_frame(
|
||||||
f.read().decode('utf-8').splitlines(),
|
f.read().decode('utf-8').splitlines(),
|
||||||
r'^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$',
|
ps_regex(4),
|
||||||
r'^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$',
|
ps_regex(4),
|
||||||
dict(
|
dict(
|
||||||
PID=lambda row: int(row['PID']),
|
PID=lambda row: int(row['PID']),
|
||||||
RSS=lambda row: int(row['RSS']),
|
RSS=lambda row: int(row['RSS']),
|
||||||
|
CPU=lambda row: float(row['%CPU']),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert set(t1.keys()) == set(['PID', 'RSS', 'USER'])
|
del t1['%CPU']
|
||||||
|
assert set(t1.keys()) == set(['PID', 'RSS', 'USER', 'CPU'])
|
||||||
|
|
||||||
t5 = subprocess.check_output(
|
t5 = subprocess.check_output(
|
||||||
'ps -e -o pid,args',
|
'ps -e -o pid,args',
|
||||||
@ -345,83 +402,135 @@ def eternal_oom(argv):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
assert set(t6.keys()) == set(['PID', 'COMMAND'])
|
assert set(t6.keys()) == set(['PID', 'COMMAND'])
|
||||||
t7 = pandas_merge(t1, t6, on='PID')
|
t11 = pandas_merge(t1, t6, on='PID')
|
||||||
|
t7 = pandas_filter_values(
|
||||||
|
t11,
|
||||||
|
lambda row: \
|
||||||
|
row['PID_x'] != self_pid and \
|
||||||
|
not 'freelancer' in row['COMMAND_y']
|
||||||
|
)
|
||||||
|
|
||||||
t8 = pandas_sort_values(
|
t8 = pandas_sort_values(
|
||||||
t7,
|
t7,
|
||||||
by=['RSS_x'],
|
by=['RSS_x'],
|
||||||
ascending=False
|
ascending=False
|
||||||
)
|
)
|
||||||
|
t9 = pandas_sort_values(
|
||||||
|
t7,
|
||||||
|
by=['CPU_x'],
|
||||||
|
ascending=False
|
||||||
|
)
|
||||||
|
t10 = sum(t7['CPU_x'], 0.0) / 100
|
||||||
|
|
||||||
return t8
|
return dict(
|
||||||
|
by_mem=t8,
|
||||||
|
by_cpu=t9,
|
||||||
|
total_cpu=t10,
|
||||||
|
)
|
||||||
|
|
||||||
|
def oom_display_rows(current_dataframe):
|
||||||
|
print('\n'.join([
|
||||||
|
(
|
||||||
|
lambda row: \
|
||||||
|
'% 8d\t% 6.3f GiB\t% 10s\t%s' % (
|
||||||
|
row['PID_x'],
|
||||||
|
row['RSS_x'] / 1024 / 1024,
|
||||||
|
row['USER_x'],
|
||||||
|
row['COMMAND_y'],
|
||||||
|
)
|
||||||
|
)(
|
||||||
|
pandas_row(current_dataframe, k)
|
||||||
|
)
|
||||||
|
for k in range(
|
||||||
|
0,
|
||||||
|
min(
|
||||||
|
5,
|
||||||
|
pandas_shape(current_dataframe)[1],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]))
|
||||||
|
|
||||||
|
def oom_kill(pid):
|
||||||
|
assert isinstance(pid, int)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logging.info('%s oom_kill, pid %d' % (
|
||||||
|
datetime.datetime.now().isoformat(),
|
||||||
|
pid,
|
||||||
|
))
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
except:
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
custom_notify(
|
||||||
|
msg='oom_kill, failed to kill pid %d' % pid
|
||||||
|
)
|
||||||
|
|
||||||
def first_check():
|
def first_check():
|
||||||
current_memory_stats = memory_stats()
|
current_memory_stats = memory_stats()
|
||||||
|
|
||||||
|
t11 = oom_get_processes()
|
||||||
|
t8 = t11['by_mem']
|
||||||
|
|
||||||
if current_memory_stats['mem_used'] > options.memory_limit:
|
if current_memory_stats['mem_used'] > options.memory_limit:
|
||||||
t8 = oom_get_processes()
|
oom_display_rows(t8)
|
||||||
print('\n'.join([
|
|
||||||
(
|
if t11['total_cpu'] > options.cpu_limit:
|
||||||
lambda row: \
|
oom_display_rows(t11['by_cpu'])
|
||||||
'% 8d\t% 6.3f GiB\t% 10s\t%s' % (
|
|
||||||
row['PID_x'],
|
|
||||||
row['RSS_x'] / 1024 / 1024,
|
|
||||||
row['USER_x'],
|
|
||||||
row['COMMAND_y'],
|
|
||||||
)
|
|
||||||
)(
|
|
||||||
pandas_row(t8, k)
|
|
||||||
)
|
|
||||||
for k in range(
|
|
||||||
0,
|
|
||||||
min(
|
|
||||||
5,
|
|
||||||
pandas_shape(t8)[1],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]))
|
|
||||||
|
|
||||||
free_before_oom = (
|
free_before_oom = (
|
||||||
options.memory_limit - current_memory_stats['mem_used']
|
options.memory_limit - current_memory_stats['mem_used']
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
'%5.2f GiB [%5.2f%%] out of %5.2f GiB of free memory before OOM' % (
|
'%5.2f %% out of %5.2f %% of cpu limit before OOC' % (
|
||||||
|
(options.cpu_limit - t11['total_cpu']) * 100 / os.cpu_count(),
|
||||||
|
options.cpu_limit * 100 / os.cpu_count(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
'%5.2f GiB [%5.2f %%] out of %5.2f GiB of free memory before OOM' % (
|
||||||
free_before_oom / 1024 / 1024,
|
free_before_oom / 1024 / 1024,
|
||||||
free_before_oom / options.memory_limit * 100,
|
free_before_oom / options.memory_limit * 100,
|
||||||
options.memory_limit / 1024 / 1024,
|
options.memory_limit / 1024 / 1024,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
print('press Enter to start monitoring')
|
del t8
|
||||||
|
del t11
|
||||||
|
|
||||||
|
print('press Enter to start monitoring: ...', end='')
|
||||||
input()
|
input()
|
||||||
|
print('\nstarted...')
|
||||||
|
|
||||||
first_check()
|
first_check()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
mem_used = memory_stats()['mem_used']
|
mem_used = memory_stats()['mem_used']
|
||||||
|
|
||||||
t8 = oom_get_processes()
|
t11 = oom_get_processes()
|
||||||
|
t8 = t11['by_mem']
|
||||||
|
|
||||||
t9 = pandas_filter_values(
|
t9 = t8
|
||||||
t8,
|
t4 = lambda : oom_kill(t9['PID_x'][0])
|
||||||
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 > options.memory_limit
|
t10 = lambda : mem_used > options.memory_limit
|
||||||
|
|
||||||
if t10():
|
if t10():
|
||||||
pprint.pprint([
|
pprint.pprint([
|
||||||
'Killing',
|
'Killing [OOM]',
|
||||||
pandas_row(t9, 0),
|
pandas_row(t9, 0),
|
||||||
mem_used,
|
mem_used,
|
||||||
])
|
])
|
||||||
t4()
|
t4()
|
||||||
|
|
||||||
|
if t11['total_cpu'] > options.cpu_limit:
|
||||||
|
pprint.pprint([
|
||||||
|
'Killing [CPU]',
|
||||||
|
pandas_row(t11['by_cpu'], 0),
|
||||||
|
t11['total_cpu'],
|
||||||
|
])
|
||||||
|
oom_kill(t11['by_cpu']['PID_x'][0])
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def resilient_vlc(stream=None):
|
def resilient_vlc(stream=None):
|
||||||
@ -985,132 +1094,121 @@ def custom_translate(current_string, check, none_char=None,):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
try:
|
def commands_cli():
|
||||||
if sys.argv[1] == 'media-play-pause':
|
logging.getLogger().setLevel(logging.INFO)
|
||||||
subprocess.check_call(['playerctl', 'play-pause'])
|
|
||||||
msg = player_metadata()
|
msg = None
|
||||||
elif sys.argv[1] == 'media-next':
|
|
||||||
subprocess.check_call(['playerctl', 'next'])
|
try:
|
||||||
msg = player_metadata()
|
if sys.argv[1] == 'media-play-pause':
|
||||||
elif sys.argv[1] == 'media-prev':
|
subprocess.check_call(['playerctl', 'play-pause'])
|
||||||
subprocess.check_call(['playerctl', 'previous'])
|
msg = player_metadata()
|
||||||
msg = player_metadata()
|
elif sys.argv[1] == 'media-next':
|
||||||
elif sys.argv[1] == 'media-lower-volume':
|
subprocess.check_call(['playerctl', 'next'])
|
||||||
subprocess.check_call([
|
msg = player_metadata()
|
||||||
'pactl',
|
elif sys.argv[1] == 'media-prev':
|
||||||
'set-sink-volume',
|
subprocess.check_call(['playerctl', 'previous'])
|
||||||
'@DEFAULT_SINK@',
|
msg = player_metadata()
|
||||||
'-5%'
|
elif sys.argv[1] == 'media-lower-volume':
|
||||||
])
|
subprocess.check_call([
|
||||||
msg = subprocess.check_output([
|
'pactl',
|
||||||
'pactl',
|
'set-sink-volume',
|
||||||
'get-sink-volume',
|
'@DEFAULT_SINK@',
|
||||||
'@DEFAULT_SINK@'
|
'-5%'
|
||||||
]).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':
|
|
||||||
http_server(sys.argv[2:])
|
|
||||||
elif sys.argv[1] == 'pass-ssh-osx':
|
|
||||||
pass_ssh_osx(sys.argv[2:])
|
|
||||||
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(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] == 'player':
|
|
||||||
player_v1(
|
|
||||||
folder_url=sys.argv[2],
|
|
||||||
item_id=int(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.extend([
|
|
||||||
subprocess.Popen(['ibus-daemon']),
|
|
||||||
subprocess.Popen(r'''
|
|
||||||
swayidle -w \
|
|
||||||
timeout 300 'swaymsg "output * dpms off"' \
|
|
||||||
resume 'swaymsg "output * dpms on"'
|
|
||||||
''', shell=True),
|
|
||||||
])
|
])
|
||||||
for o in services:
|
msg = subprocess.check_output([
|
||||||
o.wait()
|
'pactl',
|
||||||
finally:
|
'get-sink-volume',
|
||||||
for o in services:
|
'@DEFAULT_SINK@'
|
||||||
try:
|
]).decode('utf-8').strip()
|
||||||
o.terminate(timeout=10)
|
elif sys.argv[1] == 'media-raise-volume':
|
||||||
except:
|
subprocess.check_call([
|
||||||
logging.error('killed %s' % str(o.__dict__))
|
'pactl',
|
||||||
o.kill()
|
'set-sink-volume',
|
||||||
|
'@DEFAULT_SINK@',
|
||||||
else:
|
'+5%'
|
||||||
raise NotImplementedError
|
])
|
||||||
except SystemExit:
|
msg = subprocess.check_output([
|
||||||
pass
|
'pactl',
|
||||||
except:
|
'get-sink-volume',
|
||||||
msg = 'not implemented\n%s' % traceback.format_exc()
|
'@DEFAULT_SINK@'
|
||||||
logging.error(msg)
|
]).decode('utf-8').strip()
|
||||||
|
elif sys.argv[1] == 'status':
|
||||||
if not msg is None:
|
sys.stdout.write(status())
|
||||||
if sys.platform == 'darwin':
|
sys.stdout.flush()
|
||||||
subprocess.check_call([
|
elif sys.argv[1] == 'http-server':
|
||||||
'osascript',
|
http_server(sys.argv[2:])
|
||||||
'-e',
|
elif sys.argv[1] == 'pass-ssh-osx':
|
||||||
'display notification "%s" with title "commands failure"' % (
|
pass_ssh_osx(sys.argv[2:])
|
||||||
custom_translate(
|
elif sys.argv[1] == 'wl-screenshot':
|
||||||
msg,
|
subprocess.check_call(r'''
|
||||||
lambda a, b:
|
grim -g "$(slurp)" - | wl-copy
|
||||||
not re.compile(
|
''', shell=True)
|
||||||
r'^[a-zA-Z0-9\<\>\/\(\)\s\.\,\:]*$'
|
elif sys.argv[1] == 'eternal-oom':
|
||||||
)\
|
eternal_oom(sys.argv[2:])
|
||||||
.match(b) is None,
|
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':
|
||||||
else:
|
resilient_ethernet(
|
||||||
subprocess.check_call([
|
ip_addr=sys.argv[2],
|
||||||
'notify-send',
|
ethernet_device=sys.argv[3],
|
||||||
'commands',
|
)
|
||||||
msg[-128:]
|
elif sys.argv[1] == 'player':
|
||||||
])
|
player_v1(
|
||||||
|
folder_url=sys.argv[2],
|
||||||
|
item_id=int(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.extend([
|
||||||
|
subprocess.Popen(['ibus-daemon']),
|
||||||
|
subprocess.Popen(r'''
|
||||||
|
swayidle -w \
|
||||||
|
timeout 300 'swaymsg "output * dpms off"' \
|
||||||
|
resume 'swaymsg "output * dpms on"'
|
||||||
|
''', shell=True),
|
||||||
|
])
|
||||||
|
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 SystemExit:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
msg = 'not implemented\n%s' % traceback.format_exc()
|
||||||
|
logging.error(msg)
|
||||||
|
|
||||||
|
if not msg is None:
|
||||||
|
custom_notify(msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
commands_cli()
|
||||||
|
Loading…
Reference in New Issue
Block a user