#!/usr/bin/env pypy3 import sys, re from datetime import datetime def parse_realtime(text): try: date = datetime.strptime(text, "%Y/%m/%d %H:%M:%S") except ValueError: date = datetime.strptime(text, "%Y-%m-%d %H:%M:%S") return date.timestamp() def parse_gametime(text): parts = text.split(":"); return int(parts[0]) * 60 + int(parts[1]) class StateTracker: def __init__(self): self.hist = list() self.hist_pings = list() self.slots = [False] * 64; self.time_ref = None self.time = None self.time_last = None self.pcount = None self.pcount_last = None self.pings = list() def update(self): if self.time_last and self.time > self.time_last: self.hist.append((self.time_last, self.pcount_last)) self.time_last = self.time self.pcount_last = self.pcount def finish(self): if self.time != self.time_last or \ self.pcount != self.pcount_last: self.hist.append((self.time, self.pcount)) if len(self.pings): self.hist_pings.append((self.time_ref, self.pings)) self.pings = list() def ev_begin(self, realtime): self.slots = [False] * 64 self.time_ref = parse_realtime(realtime) self.time = self.time_ref self.pcount = 0 self.update() if len(self.pings): self.hist_pings.append((self.time_ref, self.pings)) self.pings = list() def ev_connect(self, gametime, slot): if self.slots[slot]: return self.slots[slot] = True self.time = self.time_ref + parse_gametime(gametime) self.pcount += 1 self.update() if self.pcount > 64: raise ValueError("too many players") def ev_disconnect(self, gametime, slot): self.slots[slot] = False self.time = self.time_ref + parse_gametime(gametime) self.pcount -= 1 self.update() def ev_endgame_stat(self, gametime, score, ping): if score == "0": return if ping == "999": return game_length = parse_gametime(gametime) self.pings.append((int(ping), game_length)) class WeightedMean: def __init__(self): self.total = 0 self.weights = 0 def feed(self, sample, weight): self.total += sample * weight; self.weights += weight def read(self): if self.weights != 0: return self.total / self.weights else: return 0 class Day: def __init__(self, date): self.date = date self.pcount_sum = 0 self.pcount_time = 0 self.pcount_peak = 0 self.pings = list() def avg_pcount(self): return self.pcount_sum / self.pcount_time def peak_pcount(self): return self.pcount_peak def ping_stats(self): mean = WeightedMean() above_60 = 0 above_110 = 0 above_160 = 0 above_210 = 0 above_260 = 0 for ping in self.pings: mean.feed(ping[0], ping[1]); if ping[0] > 60: above_60 += ping[1] if ping[0] > 110: above_110 += ping[1] if ping[0] > 160: above_160 += ping[1] if ping[0] > 210: above_210 += ping[1] if ping[0] > 260: above_260 += ping[1] if len(self.pings): above_60 /= mean.weights above_110 /= mean.weights above_160 /= mean.weights above_210 /= mean.weights above_260 /= mean.weights return "%f %f%% %f%% %f%% %f%% %f" % (mean.read(), above_60, \ above_110, above_160, above_210, above_260) class Analyzer: def __init__(self): self.time_last = None self.pcount_last = None self.days = dict() def feed(self, time, pcount): if self.time_last == None: self.time_last = time self.pcount_last = pcount return date = datetime.fromtimestamp(time).date() if date not in self.days: self.days[date] = Day(date) delta = time - self.time_last self.days[date].pcount_sum += delta * self.pcount_last self.days[date].pcount_time += delta if pcount > self.days[date].pcount_peak: self.days[date].pcount_peak = pcount self.time_last = time self.pcount_last = pcount def feed_pings(self, time, pings): date = datetime.fromtimestamp(time).date() if date not in self.days: self.days[date] = Day(date) self.days[date].pings += pings def finish(self): for date, day in self.days.items(): if day.pcount_time < 80000: continue print("%s %f %s %d" % (date, day.avg_pcount(), \ day.ping_stats(), day.peak_pcount())) pass def decoder(raw): return raw.decode("ISO-8859-1") def main(): state = StateTracker() re_realtime = re.compile("^\s*\d+:\d\d RealTime: (.*)$") re_connect = re.compile("^\s*(\d+:\d\d) ClientConnect: ([0-9]+)") re_disconnect = re.compile("^\s*(\d+:\d\d) ClientDisconnect: ([0-9]+)") re_endgame_stat = re.compile("^\s*(\d+:\d\d) score: (-?[0-9]+) ping: ([0-9]+)") for (i, line) in enumerate(map(decoder, sys.stdin.buffer)): try: if "RealTime" in line: rv = re.search(re_realtime, line) if rv: state.ev_begin(rv.group(1)) continue elif "ClientConnect" in line: rv = re.search(re_connect, line) if rv: state.ev_connect(rv.group(1), int(rv.group(2))) continue elif "ClientDisconnect" in line: rv = re.search(re_disconnect, line) if rv: state.ev_disconnect(rv.group(1), int(rv.group(2))) continue elif "score:" in line: rv = re.search(re_endgame_stat, line) if rv: state.ev_endgame_stat(rv.group(1), \ rv.group(2), \ rv.group(3)) continue except: print("ERROR on line %d:" % (i + 1), file=sys.stderr) raise state.finish() analyzer = Analyzer() for (time, count) in state.hist: analyzer.feed(time, count) for (time, pings) in state.hist_pings: analyzer.feed_pings(time, pings) analyzer.finish() main()