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,9 +1,14 @@
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'
def parse_logs():
with open('all-time.txt', 'r') as fin:
(last_t, last_ev, last_evt, last_ps) = (0, 'error', 0, [])
with open('events.json', 'w') as fout:
with open(log_path+'events.json', 'w') as fout:
cur_t = 0
cur_ps = []
@ -15,7 +20,7 @@ with open('all-time.txt', 'r') as fin:
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 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)
@ -30,33 +35,21 @@ with open('all-time.txt', 'r') as fin:
last_t = cur_t
with open('current-state.json', 'w') as fout:
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():
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')

View File

@ -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 '<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.]+)"')
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')

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>
<head>
<meta charset='utf-8'>
<!-- <meta http-equiv='refresh' content='29'> -->
<link rel='stylesheet' href='svg.css'>
<meta http-equiv='refresh' content='17'>
<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>
<body>
<nav>
<a href="day.html">last 24 hours</a>
<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}
</svg>
</div>
</body>
</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