use virtualenv, pytz, added status monitor, target folder as parameter

This commit is contained in:
User 2019-03-19 05:16:27 +01:00
parent b7b7996a3d
commit a8f78b1c70
8 changed files with 179 additions and 95 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/web/logs/
/bin/
/lib/
/lib64
/include/
/share/
/__pycache__

View File

@ -1,62 +1,55 @@
from json import loads, dumps from json import loads, dumps
from makeline import make_line
from sys import argv
with open('all-time.txt', 'r') as fin: log_path = '/dev/null'
(last_t, last_ev, last_evt, last_ps) = (0, 'error', 0, [])
with open('events.json', 'w') as fout: def parse_logs():
cur_t = 0 with open('all-time.txt', 'r') as fin:
cur_ps = [] (last_t, last_ev, last_evt, last_ps) = (0, 'error', 0, [])
for line in fin: with open(log_path+'events.json', 'w') as fout:
current = loads(line) cur_t = 0
cur_t = current['time'] cur_ps = []
cur_ps = current['players']
if last_t == 0: for line in fin:
(last_t, last_ev, last_evt, last_ps) = (cur_t, 'online', cur_t, cur_ps) current = loads(line)
else: cur_t = current['time']
if cur_t - last_t > 70: cur_ps = current['players']
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)
if last_ps != cur_ps: if last_t == 0:
fout.write(dumps({'period': [last_evt, cur_t], 'status': last_ev, 'players': last_ps})+'\n') (last_t, last_ev, last_evt, last_ps) = (cur_t, 'online', cur_t, cur_ps)
(last_ev, last_evt, last_ps) = ('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: last_t = cur_t
fout.write(dumps({'period': [last_evt, last_t], 'status': 'online', 'players': last_ps}))
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 '<line{type} x1="{start}" x2="{stop}" y1="{height}" y2="{height}"><title>{text} ({start_date}{stop_date})</title></line>'.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(): def events_to_lines():
with open('events.json', 'r') as fin: with open(log_path+'events.json', 'r') as fin:
with open('events.log', 'w') as fout: with open(log_path+'events.log', 'w') as fout:
for l in fin: for l in fin:
fout.write(make_line(loads(l))+'\n') 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')

View File

@ -1,61 +1,59 @@
from json import loads from json import loads
from datetime import datetime from datetime import datetime
from pytz import utc
from makeline import make_line, scale
from re import compile from re import compile
from sys import argv
# scale down coordinates to dx=1 per day to prevent SVG coordinate overflows log_path = '/dev/null'
scale = 1/60/60/24
def get_date(dtime): def get_lines(start):
return datetime.fromtimestamp(dtime).strftime('%Y-%m-%d %H:%M') lines = ''
def make_line(event):
evstart = event['period'][0]
evstop = event['period'][1]
return '<line{type} x1="{start}" x2="{stop}" y1="{height}" y2="{height}"><title>{text} ({start_date}{stop_date})</title></line>'.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.]+)"')
find_stop = compile(r'x2="([0-9.]+)"') 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: for l in fin:
evstart = float(find_start.search(l).group(1))
evstop = float(find_stop.search(l).group(1)) evstop = float(find_stop.search(l).group(1))
if evstop > start and evstart < stop: if evstop > start:
lines.append(l) break
with open('current-state.json', 'r') as fin: lines += l
lines.append(make_line(loads(fin.read()))) lines += fin.read()
with open(log_path+'current-state.json', 'r') as fin:
lines += make_line(loads(fin.read()))
return lines 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() stop = scale*datetime.now().timestamp()
start = stop-period start = stop-period
lines = get_lines(start, stop) day_start = scale*datetime.fromtimestamp(start/scale).replace(hour=0,minute=0,second=0,microsecond=0,tzinfo=utc).timestamp()
display_scale = 24*60/10 # 1 pixel per 10 minutes lines = get_lines(start)
with open('template.html', 'r') as fin: 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( fout.write(fin.read().format(
width = display_scale*period, width = display_scale*period,
start = start, start = start,
period = period, 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, day_stop = stop,
events = ''.join(lines) events = lines
)) ))
def make_htmls(): 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('month', 31)
make_html('4months', 120) 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')

22
makeline.py Normal file
View File

@ -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 '<line{type} x1="{start}" x2="{stop}" y1="{height}" y2="{height}"><title>{text} ({start_date}{stop_date})</title></line>'.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']
)

3
pyvenv.cfg Normal file
View File

@ -0,0 +1,3 @@
home = /usr/bin
include-system-site-packages = false
version = 3.6.7

View File

@ -2,20 +2,15 @@
<html> <html>
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<!-- <meta http-equiv='refresh' content='29'> --> <meta http-equiv='refresh' content='17'>
<link rel='stylesheet' href='svg.css'> <link rel='stylesheet' href='../css/svg.css'>
<link rel='icon' href='../img/favicon.png'>
<script src="../js/svg-click.js" charset="utf-8"></script>
</head> </head>
<body> <body>
<nav> <svg id='graph' width='{width}' height='200' viewBox='{start} -0.5 {period} 10' preserveAspectRatio='none' version='1.1' xmlns='http://www.w3.org/2000/svg'>
<a href="day.html">last 24 hours</a> <line id='days' x1='{day_start}' x2='{day_stop}'></line>
<a href="month.html">last month</a>
<a href="4months.html">last 4 months</a>
</nav>
<div class="overflow">
<svg id='graph' width='{width}' height='200' viewBox='{start} -0.5 {period} 10' preserveAspectRatio='none' version='1.1' xmlns='http://www.w3.org/2000/svg'>
<line id='days' x1='{day_start}' x2='{day_stop}'></line>
{events} {events}
</svg> </svg>
</div>
</body> </body>
</html> </html>

61
update-status.py Normal file
View File

@ -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')

5
xkcraftia-monitor.sh Executable file
View File

@ -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