summaryrefslogtreecommitdiff
path: root/stalinizer.py
diff options
context:
space:
mode:
Diffstat (limited to 'stalinizer.py')
-rwxr-xr-xstalinizer.py231
1 files changed, 231 insertions, 0 deletions
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