From a8f78b1c70c4afe46cee553809ec58268b60f9e8 Mon Sep 17 00:00:00 2001 From: User <> Date: Tue, 19 Mar 2019 05:16:27 +0100 Subject: [PATCH] use virtualenv, pytz, added status monitor, target folder as parameter --- .gitignore | 7 ++++ convert-logs.py | 89 ++++++++++++++++++++------------------------ generate-html.py | 68 ++++++++++++++++----------------- makeline.py | 22 +++++++++++ pyvenv.cfg | 3 ++ template.html | 19 ++++------ update-status.py | 61 ++++++++++++++++++++++++++++++ xkcraftia-monitor.sh | 5 +++ 8 files changed, 179 insertions(+), 95 deletions(-) create mode 100644 .gitignore create mode 100644 makeline.py create mode 100644 pyvenv.cfg create mode 100644 update-status.py create mode 100755 xkcraftia-monitor.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722f84d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/web/logs/ +/bin/ +/lib/ +/lib64 +/include/ +/share/ +/__pycache__ diff --git a/convert-logs.py b/convert-logs.py index 90c8b61..6bdac6f 100644 --- a/convert-logs.py +++ b/convert-logs.py @@ -1,62 +1,55 @@ from json import loads, dumps +from makeline import make_line +from sys import argv -with open('all-time.txt', 'r') as fin: - (last_t, last_ev, last_evt, last_ps) = (0, 'error', 0, []) +log_path = '/dev/null' - with open('events.json', 'w') as fout: - cur_t = 0 - cur_ps = [] +def parse_logs(): + with open('all-time.txt', 'r') as fin: + (last_t, last_ev, last_evt, last_ps) = (0, 'error', 0, []) - for line in fin: - current = loads(line) - cur_t = current['time'] - cur_ps = current['players'] + with open(log_path+'events.json', 'w') as fout: + cur_t = 0 + cur_ps = [] - if last_t == 0: - (last_t, last_ev, last_evt, last_ps) = (cur_t, 'online', cur_t, cur_ps) - else: - if cur_t - last_t > 70: - if last_ev == 'online': - fout.write(dumps({'period': [last_evt, last_t], 'status': last_ev, 'players': last_ps})+'\n') - (last_ev, last_evt, last_ps) = ('unreachable', last_t, cur_ps) - else: - if last_ev != 'online': - fout.write(dumps({'period': [last_evt, last_t], 'status': last_ev})+'\n') - (last_ev, last_evt, last_ps) = ('online', last_t, last_ps) + for line in fin: + current = loads(line) + cur_t = current['time'] + cur_ps = current['players'] - if last_ps != cur_ps: - fout.write(dumps({'period': [last_evt, cur_t], 'status': last_ev, 'players': last_ps})+'\n') - (last_ev, last_evt, last_ps) = ('online', cur_t, cur_ps) + if last_t == 0: + (last_t, last_ev, last_evt, last_ps) = (cur_t, 'online', cur_t, cur_ps) + else: + if cur_t - last_t > 90: + if last_ev == 'online': + fout.write(dumps({'period': [last_evt, last_t], 'status': last_ev, 'players': last_ps})+'\n') + (last_ev, last_evt, last_ps) = ('unreachable', last_t, cur_ps) + else: + if last_ev != 'online': + fout.write(dumps({'period': [last_evt, last_t], 'status': last_ev})+'\n') + (last_ev, last_evt, last_ps) = ('online', last_t, last_ps) - last_t = cur_t + if last_ps != cur_ps: + fout.write(dumps({'period': [last_evt, cur_t], 'status': last_ev, 'players': last_ps})+'\n') + (last_ev, last_evt, last_ps) = ('online', cur_t, cur_ps) - with open('current-state.json', 'w') as fout: - fout.write(dumps({'period': [last_evt, last_t], 'status': 'online', 'players': last_ps})) + last_t = cur_t + with open(log_path+'current-state.json', 'w') as fout: + fout.write(dumps({'period': [last_evt, last_t], 'status': 'online', 'players': last_ps})) - -# scale down coordinates to dx=1 per day to prevent SVG coordinate overflows -scale = 1/60/60/24 - -def get_date(dtime): - return datetime.fromtimestamp(dtime).strftime('%Y-%m-%d %H:%M') - -def make_line(event): - evstart = event['period'][0] - evstop = event['period'][1] - return '{text} ({start_date} — {stop_date})'.format( - type = '' if event['status'] == 'online' else ' class="error"', - start = scale*evstart, - stop = scale*evstop, - start_date = get_date(evstart), - stop_date = get_date(evstop), - height = len(event['players']) if 'players' in event else 0, - text = ('no one' if len(event['players']) == 0 else ', '.join(event['players'])) if event['status'] == 'online' else event['status'] - ) - def events_to_lines(): - with open('events.json', 'r') as fin: - with open('events.log', 'w') as fout: + with open(log_path+'events.json', 'r') as fin: + with open(log_path+'events.log', 'w') as fout: for l in fin: fout.write(make_line(loads(l))+'\n') + + +if __name__ == "__main__": + if len(argv) > 1: + log_path = argv[1]+'/' + parse_logs() + events_to_lines() + else: + print('error: specify target path') diff --git a/generate-html.py b/generate-html.py index 177f36f..9d8beea 100644 --- a/generate-html.py +++ b/generate-html.py @@ -1,61 +1,59 @@ from json import loads from datetime import datetime +from pytz import utc +from makeline import make_line, scale from re import compile +from sys import argv -# scale down coordinates to dx=1 per day to prevent SVG coordinate overflows -scale = 1/60/60/24 +log_path = '/dev/null' -def get_date(dtime): - return datetime.fromtimestamp(dtime).strftime('%Y-%m-%d %H:%M') - -def make_line(event): - evstart = event['period'][0] - evstop = event['period'][1] - return '{text} ({start_date} — {stop_date})'.format( - type = '' if event['status'] == 'online' else ' class="error"', - start = scale*evstart, - stop = scale*evstop, - start_date = get_date(evstart), - stop_date = get_date(evstop), - height = len(event['players']) if 'players' in event else 0, - text = ('no one' if len(event['players']) == 0 else ', '.join(event['players'])) if event['status'] == 'online' else event['status'] - ) - -def get_lines(start,stop): - lines = [] - find_start = compile(r'x1="([0-9.]+)"') +def get_lines(start): + lines = '' find_stop = compile(r'x2="([0-9.]+)"') - with open('events.log', 'r') as fin: + with open(log_path+'events.log', 'r') as fin: for l in fin: - evstart = float(find_start.search(l).group(1)) evstop = float(find_stop.search(l).group(1)) - if evstop > start and evstart < stop: - lines.append(l) - with open('current-state.json', 'r') as fin: - lines.append(make_line(loads(fin.read()))) + if evstop > start: + break + lines += l + lines += fin.read() + + with open(log_path+'current-state.json', 'r') as fin: + lines += make_line(loads(fin.read())) return lines -def make_html(name,period): + +def make_html(name, period, display_scale = 144): # display_scale = 1 pixel per 10 minutes stop = scale*datetime.now().timestamp() start = stop-period - lines = get_lines(start, stop) - display_scale = 24*60/10 # 1 pixel per 10 minutes + day_start = scale*datetime.fromtimestamp(start/scale).replace(hour=0,minute=0,second=0,microsecond=0,tzinfo=utc).timestamp() + lines = get_lines(start) + with open('template.html', 'r') as fin: - with open(name+'.html', 'w') as fout: + with open(log_path+name+'.html', 'w') as fout: fout.write(fin.read().format( width = display_scale*period, start = start, period = period, - day_start = scale*datetime.fromtimestamp(start/scale).replace(hour=0,minute=0,second=0,microsecond=0).timestamp(), + day_start = day_start, day_stop = stop, - events = ''.join(lines) + events = lines )) + def make_htmls(): - make_html('day', 1) + make_html('hour', 1/24, 24*60*4) # 4 pixels per minute + make_html('day', 1, 24*60) # 1 pixel per minute make_html('month', 31) make_html('4months', 120) + make_html('18months', 365.256*1.5, 24) # 1 pixel per hour -make_htmls() + +if __name__ == "__main__": + if len(argv) > 1: + log_path = argv[1]+'/' + make_htmls() + else: + print('error: specify target path') diff --git a/makeline.py b/makeline.py new file mode 100644 index 0000000..7621ac3 --- /dev/null +++ b/makeline.py @@ -0,0 +1,22 @@ +from datetime import datetime +from pytz import utc + +# scale down coordinates to dx=1 per day to prevent SVG coordinate overflows +scale = 1/60/60/24 + +def get_date(dtime): + return datetime.fromtimestamp(dtime).astimezone(utc).strftime('%Y-%m-%d %H:%M') + + +def make_line(event): + evstart = event['period'][0] + evstop = event['period'][1] + return '{text} ({start_date} — {stop_date})'.format( + type = '' if event['status'] == 'online' else ' class="error"', + start = scale*evstart, + stop = scale*evstop, + start_date = get_date(evstart), + stop_date = get_date(evstop), + height = len(event['players']) if 'players' in event else 0, + text = ('no one' if len(event['players']) == 0 else ', '.join(event['players'])) if event['status'] == 'online' else event['status'] + ) diff --git a/pyvenv.cfg b/pyvenv.cfg new file mode 100644 index 0000000..12a52df --- /dev/null +++ b/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.6.7 diff --git a/template.html b/template.html index de1b2d5..cf3c82c 100644 --- a/template.html +++ b/template.html @@ -2,20 +2,15 @@ - - + + + + - -
- - + + {events} - -
+ diff --git a/update-status.py b/update-status.py new file mode 100644 index 0000000..23cb4b8 --- /dev/null +++ b/update-status.py @@ -0,0 +1,61 @@ +from mcstatus import MinecraftServer +from datetime import datetime +from makeline import make_line +from json import loads, dumps +from sys import argv + +log_path = '/dev/null' + +def get_status(): + time = datetime.now().timestamp() + try: + server = MinecraftServer("xkcd.cbouton.com", 25555) + query = server.query(retries=1) + players = query.players.names + return {'time': time, 'status': 'online', 'players': players} + except: + return {'time': time, 'status': 'unreachable', 'players': []} + + +def log(event): + if event['period'][0] != event['period'][1]: + with open(log_path+'events.json', 'a') as fout: + fout.write(dumps(event)+'\n') + with open(log_path+'events.log', 'a') as fout: + fout.write(make_line(event)+'\n') + + +def new_state_and_log(): + prev = {'period': [0,0], 'status': 'undefined', 'players': []} + cur = get_status() + + with open(log_path+'current-state.json', 'r') as fin: + prev = loads(fin.read()) + + if cur['time'] - prev['period'][1] > 90: + log(prev) + log({'period': [prev['period'][1], cur['time']], 'status': 'monitor-offline' }) + return {'period': [cur['time'],cur['time']], 'status': cur['status'], 'players': cur['players']} + else: + prev['period'][1] = cur['time'] + if prev['status'] == cur['status'] and prev['players'] == cur['players']: + return prev + else: + log(prev) + return {'period': [cur['time'],cur['time']], 'status': cur['status'], 'players': cur['players']} + + +def update(): + next = new_state_and_log() + if next['status'] != 'online': + del next['players'] + + with open(log_path+'current-state.json', 'w') as fout: + fout.write(dumps(next)) + +if __name__ == "__main__": + if len(argv) > 1: + log_path = argv[1]+'/' + update() + else: + print('error: specify target path') diff --git a/xkcraftia-monitor.sh b/xkcraftia-monitor.sh new file mode 100755 index 0000000..05e50b2 --- /dev/null +++ b/xkcraftia-monitor.sh @@ -0,0 +1,5 @@ +#!/bin/bash +source bin/activate + +python3 update-status.py /var/www/xkcd.flumble.nl/logs +python3 generate-html.py /var/www/xkcd.flumble.nl/logs