From eddca01a8fcbcfb0e3bd1e2498f06cabf9ec80c8 Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Fri, 23 Feb 2018 14:45:03 +0100 Subject: Initial commit. --- stalinizer.py | 231 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100755 stalinizer.py (limited to 'stalinizer.py') diff --git a/stalinizer.py b/stalinizer.py new file mode 100755 index 0000000..2559c0c --- /dev/null +++ b/stalinizer.py @@ -0,0 +1,231 @@ +#!/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.time_ref = None + self.time = None + self.time_last = None + self.pcount = None + self.pcount_last = None + self.pings = list() + self.hist_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.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): + 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): + 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 + + 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:") + re_disconnect = re.compile("^\s*(\d+:\d\d) ClientDisconnect:") + 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)) + continue + elif "ClientDisconnect" in line: + rv = re.search(re_disconnect, line) + if rv: + state.ev_disconnect(rv.group(1)) + 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)) + 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() \ No newline at end of file -- cgit