From e996c53ed9fc99868bcd6331b1485d59441a6f32 Mon Sep 17 00:00:00 2001 From: Kjetil Andre Johannessen Date: Mon, 22 Jun 2015 22:29:38 +0200 Subject: [PATCH 1/6] Generating figure files (images) of assorted statistics --- mclogalyzer/mclogalyzer.py | 146 ++++++++++++++++++++++++++++++++++--- mclogalyzer/template.html | 15 +++- 2 files changed, 150 insertions(+), 11 deletions(-) diff --git a/mclogalyzer/mclogalyzer.py b/mclogalyzer/mclogalyzer.py index a43ce1b..b858bb9 100755 --- a/mclogalyzer/mclogalyzer.py +++ b/mclogalyzer/mclogalyzer.py @@ -25,10 +25,15 @@ import re import sys import time +import numpy +import matplotlib +matplotlib.use('Agg') # force plotter to not use an x-backend +import pylab import jinja2 + REGEX_IP = "(\d+)\.(\d+)\.(\d+)\.(\d+)" REGEX_LOGIN_USERNAME = re.compile("\[Server thread\/INFO\]: ([^]]+)\[") @@ -101,10 +106,11 @@ def __init__(self, username=""): self._username = username self._logins = 0 - self._active_days = set() - self._prev_login = None - self._first_login = None - self._last_login = None + self._day_activity = {} + self._hour_activity = [0]*24 + self._prev_login = None + self._first_login = None + self._last_login = None self._time = datetime.timedelta() self._longest_session = datetime.timedelta() @@ -123,6 +129,16 @@ def __init__(self, username=""): def handle_logout(self, date): if self._prev_login is None: return + days, hours = get_time_distribution(self._prev_login, date) + # days is a dictionary of all dates (keys) and play minutes + for d in days: + if d in self._day_activity: + self._day_activity[d] += days[d] + else: + self._day_activity[d] = days[d] + # hours is an array of 24 elements + for i in range(len(hours)): + self._hour_activity[i] += hours[i] session = date - self._prev_login self._time += session self._longest_session = max(self._longest_session, session) @@ -138,6 +154,80 @@ def track_ragequits(self, date): self._last_death_time = None + def make_plots(self, width, height): + print 'Creating figures for user ', self._username + # make daytime distribution plot + pylab.figure(1, figsize=(width/100.0, height/100.0)) + x = [] + for i in range(24): + x.append(datetime.datetime(2001, 1,1, hour=i)) + justHours = matplotlib.dates.DateFormatter('%H:%M') + pylab.plot(x, self._hour_activity, 'o-') + pylab.gca().xaxis.set_major_formatter(justHours) + pylab.xlabel('Clock'); + pylab.ylabel('Minutes played'); + pylab.title('Daytime play distribution') + pylab.savefig('img/'+self._username+'_daytime_dist.png') + pylab.clf() + + # playtime by day + today = datetime.date.today().toordinal() + start_date = today + for date in self._day_activity: + start_date = min(start_date, date) + n_days = today - start_date + 1 + n_month = datetime.date.today().day + n_week = datetime.date.today().weekday() + 1 + playtime = [0]*n_days + weektime = [0]*7 + date_tag = [] + for i in range(n_days): + date_tag.append(datetime.datetime.fromordinal(start_date + i)) + for date in self._day_activity: + playtime[date-start_date] = self._day_activity[date] + weektime[datetime.date.fromordinal(date).weekday()] += self._day_activity[date] + + # playtime by day (all history) + pylab.plot(date_tag, playtime, '.-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.xlabel('Date'); + pylab.ylabel('Minutes played'); + pylab.title('Play minutes per day') + pylab.savefig('img/'+self._username+'_day_history.png') + pylab.clf() + + # playtime by day (current month) + pylab.plot(date_tag[-n_month:], playtime[-n_month:], '.-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.xlabel('Date'); + pylab.ylabel('Minutes played'); + pylab.title('Play minutes per day this month') + pylab.savefig('img/'+self._username+'_day_month.png') + pylab.clf() + + # playtime by day (current week) + pylab.plot(date_tag[-n_week:], playtime[-n_week:], '.-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.gca().xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(n_week+1)) + pylab.gca().xaxis.set_minor_locator(matplotlib.ticker.MaxNLocator(1)) + matplotlib.ticker.MaxNLocator + pylab.xlabel('Date'); + pylab.ylabel('Minutes played'); + pylab.title('Play minutes per day this week') + pylab.savefig('img/'+self._username+'_day_week.png') + pylab.clf() + + # plot weekday pie chart + labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' + explode= [.03]*7 + pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True) + pylab.title('Playtime per weekday') #, bbox={'facecolor':'0.8', 'pad':5}) + pylab.savefig('img/'+self._username+'_weekday_pie.png') + pylab.clf() + @property def username(self): return self._username @@ -157,7 +247,7 @@ def time_per_login(self): @property def active_days(self): - return len(self._active_days) + return len(self._day_activity) @property def time_per_active_day(self): @@ -251,6 +341,7 @@ def __init__(self): self._time_played = datetime.timedelta() self._max_players = 0 self._max_players_date = None + self._include_figures = False @property def statistics_since(self): @@ -268,6 +359,10 @@ def max_players(self): def max_players_date(self): return self._max_players_date + @property + def include_figures(self): + return self._include_figures + def grep_logname_date(line): try: @@ -334,6 +429,23 @@ def grep_achievement(line): username = search.group(1) return username.decode("ascii", "ignore").encode("ascii", "ignore"), search.group(2) +# returns number of minutes played during each clock hour and date between start and end +def get_time_distribution(start, end): + hours = [0]*24 + days = {} + timeleft = end-start + time_iterate = start + while time_iterate < end: + next_hour = (time_iterate + datetime.timedelta(hours=1)).replace(minute=0, second=0) + dt = min(next_hour-time_iterate, end-time_iterate) + hours[time_iterate.hour] += dt.seconds/60.0 + dateKey = time_iterate.date().toordinal() + if dateKey in days: + days[dateKey] += dt.seconds/60.0 + else: + days[dateKey] = dt.seconds/60.0 + time_iterate += dt + return days, hours def format_delta(timedelta, days=True, maybe_years=False): seconds = timedelta.seconds @@ -393,7 +505,6 @@ def parse_logs(logdir, since=None, whitelist_users=None): if username not in users: users[username] = UserStats(username) user = users[username] - user._active_days.add((date.year, date.month, date.day)) user._logins += 1 user._last_login = user._prev_login = date if user._first_login is None: @@ -421,7 +532,6 @@ def parse_logs(logdir, since=None, whitelist_users=None): continue user = users[username] - user._active_days.add((date.year, date.month, date.day)) user.handle_logout(date) if username in online_players: online_players.remove(username) @@ -510,6 +620,15 @@ def main(): parser.add_argument("--chat", action='store_true', help="display the general chat log") + parser.add_argument("--figures", + action='store_true', + help="generate statistic figures (stored in \"img\" folder)") + parser.add_argument("--figuresize", + nargs=2, + default=(800,600), + type=int, + help="figure size (in pixels) for all generated plots", + metavar="") parser.add_argument("logdir", help="the server log directory", metavar="") @@ -533,22 +652,31 @@ def main(): whitelist_users = parse_whitelist(args["whitelist"]) if args["whitelist"] else None users, server, chats = parse_logs(args["logdir"], since, whitelist_users) + if not args['chat']: chats = [] # ignore chat messages + if args['figures']: + if not os.path.isdir('img'): + os.makedirs('img') # should include error testing if process does not have the right write permissions + server._include_figures = True + figure_width = args['figuresize'][0] + figure_height = args['figuresize'][1] template_path = os.path.join(os.path.dirname(__file__), "template.html") if args["template"] is not None: template_path = args["template"] template_dir = os.path.dirname(template_path) template_name = os.path.basename(template_path) - #print template_path - #print template_dir, template_name if not os.path.exists(template_path): print "Unable to find template file %s!" % template_path sys.exit(1) env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) template = env.get_template(template_name) + + if server._include_figures: + for u in users: + u.make_plots(figure_width, figure_height) f = open(args["output"], "w") f.write(template.render(users=users, diff --git a/mclogalyzer/template.html b/mclogalyzer/template.html index b2c666e..f2380d8 100644 --- a/mclogalyzer/template.html +++ b/mclogalyzer/template.html @@ -135,9 +135,9 @@ {% endif %} + {% if server.include_figures %} + + + + + + + + + + {% endif %}
From 707d343f6a2e8b48fd81f6911a15ace61214ccee Mon Sep 17 00:00:00 2001 From: Kjetil Andre Johannessen Date: Tue, 23 Jun 2015 11:23:54 +0200 Subject: [PATCH 2/6] Added serverwide use statistic figures (playtime by weekday/clockhour/date) --- mclogalyzer/mclogalyzer.py | 97 ++++++++++++++++++++++++++++++++++++++ mclogalyzer/template.html | 13 ++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/mclogalyzer/mclogalyzer.py b/mclogalyzer/mclogalyzer.py index b858bb9..4944902 100755 --- a/mclogalyzer/mclogalyzer.py +++ b/mclogalyzer/mclogalyzer.py @@ -117,6 +117,8 @@ def __init__(self, username=""): self._death_count = 0 self._death_types = {} + self._include_figures = False + # Rage quit tracking self._last_death_time = None self._ragequits = 0 @@ -164,6 +166,7 @@ def make_plots(self, width, height): justHours = matplotlib.dates.DateFormatter('%H:%M') pylab.plot(x, self._hour_activity, 'o-') pylab.gca().xaxis.set_major_formatter(justHours) + pylab.setp(pylab.xticks()[1], rotation=20) pylab.xlabel('Clock'); pylab.ylabel('Minutes played'); pylab.title('Daytime play distribution') @@ -297,6 +300,10 @@ def achievements(self): def ragequit_count(self): return self._ragequits + @property + def include_figures(self): + return self._include_figures + class ChatLog: def __init__(self, timestamp, user, msg): self._time = str("%02d:%02d:%02d"%(timestamp.hour,timestamp.minute,timestamp.second)) @@ -343,6 +350,90 @@ def __init__(self): self._max_players_date = None self._include_figures = False + self._day_activity = {} + self._hour_activity = [0]*24 + + def add_activity(self, user): + for d in user._day_activity: + if d in self._day_activity: + self._day_activity[d] += user._day_activity[d] + else: + self._day_activity[d] = user._day_activity[d] + for i in range(len(self._hour_activity)): + self._hour_activity[i] += user._hour_activity[i] + + def make_plots(self, width, height): + print 'Creating figures for server' + # make daytime distribution plot + pylab.figure(1, figsize=(width/100.0, height/100.0)) + x = [] + for i in range(24): + x.append(datetime.datetime(2001, 1,1, hour=i)) + justHours = matplotlib.dates.DateFormatter('%H:%M') + pylab.plot(x, self._hour_activity, 'o-') + pylab.gca().xaxis.set_major_formatter(justHours) + pylab.xlabel('Clock'); + pylab.ylabel('Minutes played'); + pylab.title('Daytime play distribution') + pylab.savefig('img/server_daytime_dist.png') + pylab.clf() + + # playtime by day + today = datetime.date.today().toordinal() + start_date = self._statistics_since.toordinal() + n_days = today - start_date + 1 + n_month = datetime.date.today().day + n_week = datetime.date.today().weekday() + 1 + playtime = [0]*n_days + weektime = [0]*7 + date_tag = [] + for i in range(n_days): + date_tag.append(datetime.datetime.fromordinal(start_date + i)) + for date in self._day_activity: + playtime[date-start_date] = self._day_activity[date] + weektime[datetime.date.fromordinal(date).weekday()] += self._day_activity[date] + + # playtime by day (all history) + pylab.plot(date_tag, playtime, '.-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.xlabel('Date'); + pylab.ylabel('Minutes played'); + pylab.title('Play minutes per day') + pylab.savefig('img/server_day_history.png') + pylab.clf() + + # playtime by day (current month) + pylab.plot(date_tag[-n_month:], playtime[-n_month:], '.-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.xlabel('Date'); + pylab.ylabel('Minutes played'); + pylab.title('Play minutes per day this month') + pylab.savefig('img/server_day_month.png') + pylab.clf() + + # playtime by day (current week) + pylab.plot(date_tag[-n_week:], playtime[-n_week:], '.-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.gca().xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(n_week+1)) + pylab.gca().xaxis.set_minor_locator(matplotlib.ticker.MaxNLocator(1)) + matplotlib.ticker.MaxNLocator + pylab.xlabel('Date'); + pylab.ylabel('Minutes played'); + pylab.title('Play minutes per day this week') + pylab.savefig('img/server_day_week.png') + pylab.clf() + + # plot weekday pie chart + labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' + explode= [.03]*7 + pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True) + pylab.title('Playtime per weekday') #, bbox={'facecolor':'0.8', 'pad':5}) + pylab.savefig('img/server_weekday_pie.png') + pylab.clf() + @property def statistics_since(self): return self._statistics_since @@ -653,6 +744,10 @@ def main(): whitelist_users = parse_whitelist(args["whitelist"]) if args["whitelist"] else None users, server, chats = parse_logs(args["logdir"], since, whitelist_users) + # sum up the use statistics for all single users to get serverwide results + for u in users: + server.add_activity(u) + if not args['chat']: chats = [] # ignore chat messages if args['figures']: @@ -676,7 +771,9 @@ def main(): if server._include_figures: for u in users: + u._include_figures = True u.make_plots(figure_width, figure_height) + server.make_plots(figure_width, figure_height) f = open(args["output"], "w") f.write(template.render(users=users, diff --git a/mclogalyzer/template.html b/mclogalyzer/template.html index f2380d8..16b770b 100644 --- a/mclogalyzer/template.html +++ b/mclogalyzer/template.html @@ -218,6 +218,17 @@

Columns

Maximum online players: {{ server.max_players }} ({{ server.max_players_date }}) + {% if server.include_figures %} + + + + + + + + + + {% endif %}
@@ -324,7 +335,7 @@

Player Statistics for {{user.username }}

{% endif %} - {% if server.include_figures %} + {% if user.include_figures %} From 6c0d4beeb0f7391d071293bb21fc7b716d9489f4 Mon Sep 17 00:00:00 2001 From: Kjetil Andre Johannessen Date: Tue, 23 Jun 2015 14:44:49 +0200 Subject: [PATCH 3/6] Upgraded the apperance of figures. Looks much nicer now --- mclogalyzer/mclogalyzer.py | 72 +++++++++++++++++++++++++++----------- mclogalyzer/template.html | 3 ++ 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/mclogalyzer/mclogalyzer.py b/mclogalyzer/mclogalyzer.py index 4944902..d34f05a 100755 --- a/mclogalyzer/mclogalyzer.py +++ b/mclogalyzer/mclogalyzer.py @@ -158,18 +158,25 @@ def track_ragequits(self, date): def make_plots(self, width, height): print 'Creating figures for user ', self._username - # make daytime distribution plot + # create figure holder used for all plots pylab.figure(1, figsize=(width/100.0, height/100.0)) + + ### make daytime distribution plot + # create x-axis time stamps x = [] for i in range(24): x.append(datetime.datetime(2001, 1,1, hour=i)) + # do the actual plot + pylab.bar(x, self._hour_activity, width=1.0/24.0) + # format to make it pretty justHours = matplotlib.dates.DateFormatter('%H:%M') - pylab.plot(x, self._hour_activity, 'o-') - pylab.gca().xaxis.set_major_formatter(justHours) pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(justHours) pylab.xlabel('Clock'); + pylab.xlim(x[0], x[-1]) pylab.ylabel('Minutes played'); pylab.title('Daytime play distribution') + # save, clear and continue with the rest of the plots pylab.savefig('img/'+self._username+'_daytime_dist.png') pylab.clf() @@ -191,7 +198,7 @@ def make_plots(self, width, height): weektime[datetime.date.fromordinal(date).weekday()] += self._day_activity[date] # playtime by day (all history) - pylab.plot(date_tag, playtime, '.-') + pylab.plot(date_tag, playtime) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); @@ -201,7 +208,7 @@ def make_plots(self, width, height): pylab.clf() # playtime by day (current month) - pylab.plot(date_tag[-n_month:], playtime[-n_month:], '.-') + pylab.plot(date_tag[-n_month:], playtime[-n_month:]) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); @@ -211,7 +218,7 @@ def make_plots(self, width, height): pylab.clf() # playtime by day (current week) - pylab.plot(date_tag[-n_week:], playtime[-n_week:], '.-') + pylab.plot(date_tag[-n_week:], playtime[-n_week:]) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.gca().xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(n_week+1)) @@ -226,7 +233,8 @@ def make_plots(self, width, height): # plot weekday pie chart labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' explode= [.03]*7 - pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True) + colors = ('#FF5050', '#009933', '#9966FF', '#996600', '#4D9494', '#C28585', '#3366CC') + pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True, colors=colors) pylab.title('Playtime per weekday') #, bbox={'facecolor':'0.8', 'pad':5}) pylab.savefig('img/'+self._username+'_weekday_pie.png') pylab.clf() @@ -350,31 +358,42 @@ def __init__(self): self._max_players_date = None self._include_figures = False - self._day_activity = {} + self._day_play_minutes = {} + self._day_active_users = {} self._hour_activity = [0]*24 def add_activity(self, user): for d in user._day_activity: - if d in self._day_activity: - self._day_activity[d] += user._day_activity[d] + if d in self._day_play_minutes: + self._day_play_minutes[d] += user._day_activity[d] + self._day_active_users[d] += 1 else: - self._day_activity[d] = user._day_activity[d] + self._day_play_minutes[d] = user._day_activity[d] + self._day_active_users[d] = 1 for i in range(len(self._hour_activity)): self._hour_activity[i] += user._hour_activity[i] def make_plots(self, width, height): print 'Creating figures for server' - # make daytime distribution plot + # create figure holder used for all plots pylab.figure(1, figsize=(width/100.0, height/100.0)) + + ### make daytime distribution plot + # create x-axis time stamps x = [] for i in range(24): x.append(datetime.datetime(2001, 1,1, hour=i)) + # do the actual plot + pylab.bar(x, self._hour_activity, width=1.0/24.0) + # format to make it pretty justHours = matplotlib.dates.DateFormatter('%H:%M') - pylab.plot(x, self._hour_activity, 'o-') + pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(justHours) pylab.xlabel('Clock'); + pylab.xlim(x[0], x[-1]) pylab.ylabel('Minutes played'); pylab.title('Daytime play distribution') + # save, clear and continue with the rest of the plots pylab.savefig('img/server_daytime_dist.png') pylab.clf() @@ -385,16 +404,18 @@ def make_plots(self, width, height): n_month = datetime.date.today().day n_week = datetime.date.today().weekday() + 1 playtime = [0]*n_days + users = [0]*n_days weektime = [0]*7 date_tag = [] for i in range(n_days): date_tag.append(datetime.datetime.fromordinal(start_date + i)) - for date in self._day_activity: - playtime[date-start_date] = self._day_activity[date] - weektime[datetime.date.fromordinal(date).weekday()] += self._day_activity[date] + for date in self._day_play_minutes: + playtime[date-start_date] = self._day_play_minutes[date] + users[ date-start_date] = self._day_active_users[date] + weektime[datetime.date.fromordinal(date).weekday()] += self._day_play_minutes[date] # playtime by day (all history) - pylab.plot(date_tag, playtime, '.-') + pylab.plot(date_tag, playtime) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); @@ -403,8 +424,18 @@ def make_plots(self, width, height): pylab.savefig('img/server_day_history.png') pylab.clf() + # active users by day (all history) + pylab.plot(date_tag, users) + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.xlabel('Date'); + pylab.ylabel('Active players'); + pylab.title('Online players per day') + pylab.savefig('img/server_day_users.png') + pylab.clf() + # playtime by day (current month) - pylab.plot(date_tag[-n_month:], playtime[-n_month:], '.-') + pylab.plot(date_tag[-n_month:], playtime[-n_month:]) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); @@ -414,7 +445,7 @@ def make_plots(self, width, height): pylab.clf() # playtime by day (current week) - pylab.plot(date_tag[-n_week:], playtime[-n_week:], '.-') + pylab.plot(date_tag[-n_week:], playtime[-n_week:]) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.gca().xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(n_week+1)) @@ -429,7 +460,8 @@ def make_plots(self, width, height): # plot weekday pie chart labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' explode= [.03]*7 - pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True) + colors = ('#FF5050', '#009933', '#9966FF', '#996600', '#4D9494', '#C28585', '#3366CC') + pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True, colors=colors) pylab.title('Playtime per weekday') #, bbox={'facecolor':'0.8', 'pad':5}) pylab.savefig('img/server_weekday_pie.png') pylab.clf() diff --git a/mclogalyzer/template.html b/mclogalyzer/template.html index 16b770b..b922c36 100644 --- a/mclogalyzer/template.html +++ b/mclogalyzer/template.html @@ -228,6 +228,9 @@

Columns

+ + + {% endif %}
From ed2b10f0215b6aef52b813c17ed6e2cca36c688c Mon Sep 17 00:00:00 2001 From: Kjetil Andre Johannessen Date: Sun, 28 Jun 2015 01:07:08 +0200 Subject: [PATCH 4/6] Added stats for pvp kills/deaths --- mclogalyzer/mclogalyzer.py | 62 +++++++++++++++++++++++++++++++++----- mclogalyzer/template.html | 17 +++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/mclogalyzer/mclogalyzer.py b/mclogalyzer/mclogalyzer.py index d34f05a..6561149 100755 --- a/mclogalyzer/mclogalyzer.py +++ b/mclogalyzer/mclogalyzer.py @@ -25,7 +25,6 @@ import re import sys import time -import numpy import matplotlib matplotlib.use('Agg') # force plotter to not use an x-backend import pylab @@ -117,6 +116,9 @@ def __init__(self, username=""): self._death_count = 0 self._death_types = {} + self._pvp_kills = {} + self._pvp_deaths = 0 + self._include_figures = False # Rage quit tracking @@ -296,6 +298,17 @@ def death_count(self): def death_types(self): return sorted(self._death_types.items(), key=lambda k: k[1]) + @property + def pvp_kills(self): + kills = 0 + for user in self._pvp_kills: + kills += self._pvp_kills[user] + return kills + + @property + def pvp_deaths(self): + return self._pvp_deaths + @property def achievement_count(self): return self._achievement_count @@ -360,6 +373,7 @@ def __init__(self): self._day_play_minutes = {} self._day_active_users = {} + self._day_pvp_kills = {} self._hour_activity = [0]*24 def add_activity(self, user): @@ -405,14 +419,18 @@ def make_plots(self, width, height): n_week = datetime.date.today().weekday() + 1 playtime = [0]*n_days users = [0]*n_days + pvp_kills= [0]*n_days weektime = [0]*7 date_tag = [] for i in range(n_days): date_tag.append(datetime.datetime.fromordinal(start_date + i)) for date in self._day_play_minutes: - playtime[date-start_date] = self._day_play_minutes[date] - users[ date-start_date] = self._day_active_users[date] - weektime[datetime.date.fromordinal(date).weekday()] += self._day_play_minutes[date] + weekday = datetime.date.fromordinal(date).weekday() + playtime[date-start_date] = self._day_play_minutes[date] + users[ date-start_date] = self._day_active_users[date] + weektime[weekday] += self._day_play_minutes[date] + for date in self._day_pvp_kills: + pvp_kills[date-start_date] = self._day_pvp_kills[date]; # playtime by day (all history) pylab.plot(date_tag, playtime) @@ -425,7 +443,7 @@ def make_plots(self, width, height): pylab.clf() # active users by day (all history) - pylab.plot(date_tag, users) + pylab.plot(date_tag, users, 'm-') pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); @@ -434,6 +452,16 @@ def make_plots(self, width, height): pylab.savefig('img/server_day_users.png') pylab.clf() + # pvp kills per day + pylab.plot(date_tag, pvp_kills, 'r-') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) + pylab.xlabel('Date'); + pylab.ylabel('PvP Kills'); + pylab.title('Server hostility: PvP kills per day') + pylab.savefig('img/server_day_pvp.png') + pylab.clf() + # playtime by day (current month) pylab.plot(date_tag[-n_month:], playtime[-n_month:]) pylab.setp(pylab.xticks()[1], rotation=20) @@ -538,8 +566,9 @@ def grep_death(line): for regex in REGEX_DEATH_MESSAGES: search = regex.search(line) if search: - return search.group(1), capitalize_first(search.group(2)) - return None, None + words = line.split(' ') + return search.group(1), capitalize_first(search.group(2)), words[-1] + return None, None, None def grep_chatlog(line): search @@ -676,7 +705,7 @@ def parse_logs(logdir, since=None, whitelist_users=None): achievement_user._achievement_count += 1 achievement_user._achievements.append(achievement) else: - death_username, death_type = grep_death(line) + death_username, death_type, last_word = grep_death(line) death_time = grep_log_datetime(today, line) if death_username is not None: if death_username in users: @@ -686,6 +715,17 @@ def parse_logs(logdir, since=None, whitelist_users=None): if death_type not in death_user._death_types: death_user._death_types[death_type] = 0 death_user._death_types[death_type] += 1 + if last_word in users: + kill_user = users[last_word] + if death_user in kill_user._pvp_kills: + kill_user._pvp_kills[death_user] += 1 + else: + kill_user._pvp_kills[death_user] = 1 + kill_day = death_time.date().toordinal() + if kill_day in server._day_pvp_kills: + server._day_pvp_kills[kill_day] += 1 + else: + server._day_pvp_kills[kill_day] = 1 else: date = grep_log_datetime(today, line) search = REGEX_CHAT_USERNAME.search(line) @@ -702,6 +742,12 @@ def parse_logs(logdir, since=None, whitelist_users=None): thisChatDay._chat = thisChatDay._chat[::-1] # reverse chat list so newest on top chat = chat[::-1] + # update pvp deaths + for killername in users: + killer = users[killername] + for victim in killer._pvp_kills: + victim._pvp_deaths += killer._pvp_kills[victim] + if whitelist_users is not None: for username in whitelist_users: if username not in users: diff --git a/mclogalyzer/template.html b/mclogalyzer/template.html index b922c36..018bf89 100644 --- a/mclogalyzer/template.html +++ b/mclogalyzer/template.html @@ -37,6 +37,8 @@ [".c-last-login", "Last login", false], [".c-longest-session", "Longest session", false], [".c-deaths", "Deaths", false], + [".c-pvp-kills", "PvP kills", false], + [".c-pvp-deaths", "PvP deaths", false], [".c-ragequits", "Rage quits", false], [".c-achievements", "Achievements", false], [".c-active-days", "Active days", false], @@ -164,6 +166,8 @@

Columns

Last login Longest session Deaths + PvP Kills + PvP Deaths Rage quits Achievements Active days @@ -188,6 +192,8 @@

Columns

{{ user.last_login }} {{ user.longest_session }} {{ user.death_count }} + {{ user.pvp_kills }} + {{ user.pvp_deaths }} {{ user.ragequit_count }} {{ user.achievement_count }} {{ user.active_days }} @@ -231,6 +237,9 @@

Columns

+ + + {% endif %} @@ -320,6 +329,14 @@

Player Statistics for {{user.username }}

{% endif %} + + PvP Kills: + {{ user.pvp_kills}} + + + PvP Deaths: + {{ user.pvp_deaths}} + Rage quits: {{ user.ragequit_count}} From d1ec079d3f262903bbcd535c7399a01d3da94e6f Mon Sep 17 00:00:00 2001 From: Kjetil Andre Johannessen Date: Fri, 10 Jul 2015 17:13:49 +0200 Subject: [PATCH 5/6] Removed the creation of unused figure files --- mclogalyzer/mclogalyzer.py | 50 ++------------------------------------ 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/mclogalyzer/mclogalyzer.py b/mclogalyzer/mclogalyzer.py index 6561149..7ea8b36 100755 --- a/mclogalyzer/mclogalyzer.py +++ b/mclogalyzer/mclogalyzer.py @@ -199,7 +199,7 @@ def make_plots(self, width, height): playtime[date-start_date] = self._day_activity[date] weektime[datetime.date.fromordinal(date).weekday()] += self._day_activity[date] - # playtime by day (all history) + # playtime by day pylab.plot(date_tag, playtime) pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) @@ -209,29 +209,6 @@ def make_plots(self, width, height): pylab.savefig('img/'+self._username+'_day_history.png') pylab.clf() - # playtime by day (current month) - pylab.plot(date_tag[-n_month:], playtime[-n_month:]) - pylab.setp(pylab.xticks()[1], rotation=20) - pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) - pylab.xlabel('Date'); - pylab.ylabel('Minutes played'); - pylab.title('Play minutes per day this month') - pylab.savefig('img/'+self._username+'_day_month.png') - pylab.clf() - - # playtime by day (current week) - pylab.plot(date_tag[-n_week:], playtime[-n_week:]) - pylab.setp(pylab.xticks()[1], rotation=20) - pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) - pylab.gca().xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(n_week+1)) - pylab.gca().xaxis.set_minor_locator(matplotlib.ticker.MaxNLocator(1)) - matplotlib.ticker.MaxNLocator - pylab.xlabel('Date'); - pylab.ylabel('Minutes played'); - pylab.title('Play minutes per day this week') - pylab.savefig('img/'+self._username+'_day_week.png') - pylab.clf() - # plot weekday pie chart labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' explode= [.03]*7 @@ -442,7 +419,7 @@ def make_plots(self, width, height): pylab.savefig('img/server_day_history.png') pylab.clf() - # active users by day (all history) + # active users by day pylab.plot(date_tag, users, 'm-') pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) @@ -462,29 +439,6 @@ def make_plots(self, width, height): pylab.savefig('img/server_day_pvp.png') pylab.clf() - # playtime by day (current month) - pylab.plot(date_tag[-n_month:], playtime[-n_month:]) - pylab.setp(pylab.xticks()[1], rotation=20) - pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) - pylab.xlabel('Date'); - pylab.ylabel('Minutes played'); - pylab.title('Play minutes per day this month') - pylab.savefig('img/server_day_month.png') - pylab.clf() - - # playtime by day (current week) - pylab.plot(date_tag[-n_week:], playtime[-n_week:]) - pylab.setp(pylab.xticks()[1], rotation=20) - pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) - pylab.gca().xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(n_week+1)) - pylab.gca().xaxis.set_minor_locator(matplotlib.ticker.MaxNLocator(1)) - matplotlib.ticker.MaxNLocator - pylab.xlabel('Date'); - pylab.ylabel('Minutes played'); - pylab.title('Play minutes per day this week') - pylab.savefig('img/server_day_week.png') - pylab.clf() - # plot weekday pie chart labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' explode= [.03]*7 From 1961894ba9c1d9547fa2d9f9c935f932f3686dc3 Mon Sep 17 00:00:00 2001 From: Kjetil Andre Johannessen Date: Thu, 16 Jul 2015 23:49:44 +0200 Subject: [PATCH 6/6] Added full documentation (epydoc) --- README.md | 11 ++ mclogalyzer/mclogalyzer.py | 280 +++++++++++++++++++++++++++++-------- 2 files changed, 229 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index e403086..e90d91e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,14 @@ Now you can run it with `mclogalyzer`. Alternatively you can directly run the script `mclogalyzer/mclogalyzer.py`. +## Development ## + +The files are documented by epydoc standards. To generate the documentation call +``` +epydoc mclogalyzer/mclogalyzer.py +``` +which generates the documenation in the html folder. + ## How to use the script ## You have to pass the path to the Minecraft server log directory and the path to an @@ -53,3 +61,6 @@ to generate the report of last month or last week. You can use the whitelist as a guide using `--w whitelist.json`. If so, users not included in the whitelist will be removed from final results, and users not present in log file but present in whitelist will be added to the output. + +You can add full chatlog history by providing `--chat`. This will result in +large file size and provide a chat tab at the top menu. diff --git a/mclogalyzer/mclogalyzer.py b/mclogalyzer/mclogalyzer.py index 7ea8b36..659fbd8 100755 --- a/mclogalyzer/mclogalyzer.py +++ b/mclogalyzer/mclogalyzer.py @@ -91,21 +91,27 @@ # Got this value from http://minecraft.gamepedia.com/Achievements ACHIEVEMENTS_AVAILABLE = 34 -# Maximum duration, in seconds, that a logout can be considered a ragequit RAGEQUIT_MAX_ELAPSED_TIME = 45 +""" Maximum duration, in seconds, that a logout can be considered a ragequit """ def capitalize_first(str): + """ Returns the argument string with the first character capitalized """ if not len(str): return "" return str[:1].upper() + str[1:] class UserStats: + """ Class containing all stats for one particular user. This includes, but are not limited to username, playtime, kills and achivements """ def __init__(self, username=""): + """ Constructor + @param username: Minecraft login username + @type username: String + """ self._username = username self._logins = 0 - self._day_activity = {} + self._day_activity = {} self._hour_activity = [0]*24 self._prev_login = None self._first_login = None @@ -131,6 +137,11 @@ def __init__(self, username=""): self._achievements = [] def handle_logout(self, date): + """ Count up use statistic since self._prev_login was set + @param date: Timestamp of logout date and time + @type date: datetime.datetime + @return : None + """ if self._prev_login is None: return days, hours = get_time_distribution(self._prev_login, date) @@ -151,6 +162,11 @@ def handle_logout(self, date): def track_ragequits(self, date): + """ Called on logout to check if time since last death is short enough to warrant a ragequit tag + @param date: Timestamp of logout date and time + @type date: datetime.datetime + @return : None + """ if self._last_death_time: elapsed_death_to_logout = (date - self._last_death_time).total_seconds() if elapsed_death_to_logout <= RAGEQUIT_MAX_ELAPSED_TIME: @@ -159,109 +175,133 @@ def track_ragequits(self, date): self._last_death_time = None def make_plots(self, width, height): + """ Create images of use statistic and store these in the 'img' folder + @param width : Figure width in pixels + @type width : Int + @param height: Figure height in pixels + @type height: Int + @return : None + """ print 'Creating figures for user ', self._username # create figure holder used for all plots pylab.figure(1, figsize=(width/100.0, height/100.0)) - ### make daytime distribution plot - # create x-axis time stamps - x = [] + ### pre-evaluate a few variables needed to do the plotting + clock_tag = [] # 24-hour clock tag for x-axis for i in range(24): - x.append(datetime.datetime(2001, 1,1, hour=i)) - # do the actual plot - pylab.bar(x, self._hour_activity, width=1.0/24.0) - # format to make it pretty - justHours = matplotlib.dates.DateFormatter('%H:%M') - pylab.setp(pylab.xticks()[1], rotation=20) - pylab.gca().xaxis.set_major_formatter(justHours) - pylab.xlabel('Clock'); - pylab.xlim(x[0], x[-1]) - pylab.ylabel('Minutes played'); - pylab.title('Daytime play distribution') - # save, clear and continue with the rest of the plots - pylab.savefig('img/'+self._username+'_daytime_dist.png') - pylab.clf() - - # playtime by day + clock_tag.append(datetime.datetime(2001, 1,1, hour=i)) + # playtime by day (all history, last week and last month) today = datetime.date.today().toordinal() start_date = today for date in self._day_activity: start_date = min(start_date, date) - n_days = today - start_date + 1 - n_month = datetime.date.today().day - n_week = datetime.date.today().weekday() + 1 + n_days = today - start_date + 1 # days in recorded history + n_month = datetime.date.today().day # days in this month + n_week = datetime.date.today().weekday() + 1 # days in this week playtime = [0]*n_days weektime = [0]*7 - date_tag = [] + date_tag = [] # date tags for x-axis on history plots for i in range(n_days): date_tag.append(datetime.datetime.fromordinal(start_date + i)) for date in self._day_activity: playtime[date-start_date] = self._day_activity[date] weektime[datetime.date.fromordinal(date).weekday()] += self._day_activity[date] - # playtime by day + ### make daytime distribution plot (play minutes by clock hour) + # do the actual plot + pylab.bar(clock_tag, self._hour_activity, width=1.0/24.0) + # format to make it pretty + justHours = matplotlib.dates.DateFormatter('%H:%M') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(justHours) + pylab.xlabel('Clock'); + pylab.xlim(clock_tag[0], clock_tag[-1]) + pylab.ylabel('Minutes played'); + pylab.title('Daytime play distribution') + # save, clear and continue with the rest of the plots + pylab.savefig('img/'+self._username+'_daytime_dist.png') + pylab.clf() + + ### make date playtime distribution plot (play minutes by date) + # do the actual plot pylab.plot(date_tag, playtime) + # format to make it pretty pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); pylab.ylabel('Minutes played'); pylab.title('Play minutes per day') + # save, clear and continue with the rest of the plots pylab.savefig('img/'+self._username+'_day_history.png') pylab.clf() - # plot weekday pie chart + ### make weekday playtime pie chart labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' explode= [.03]*7 colors = ('#FF5050', '#009933', '#9966FF', '#996600', '#4D9494', '#C28585', '#3366CC') + # do the actual plot pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True, colors=colors) - pylab.title('Playtime per weekday') #, bbox={'facecolor':'0.8', 'pad':5}) + # format to make it pretty + pylab.title('Playtime per weekday') + # save, clear and continue with the rest of the plots pylab.savefig('img/'+self._username+'_weekday_pie.png') pylab.clf() @property def username(self): + """ Minecraft username of this user """ return self._username @property def logins(self): + """ Number of times logged in """ return self._logins @property def time(self): + """ Total amount of playtime (formatted string) """ return format_delta(self._time) @property def time_per_login(self): + """ Average playtime per login (formatted string) """ return format_delta( self._time / self._logins if self._logins != 0 else datetime.timedelta(), False) @property def active_days(self): + """ Number of unique days logged in to server """ return len(self._day_activity) @property def time_per_active_day(self): + """ Average time spent per day played (formatted string) """ return format_delta( self._time / self.active_days if self.active_days != 0 else datetime.timedelta(), False) @property def first_login(self): + """ First date logged in to server """ return str(self._first_login) @property def last_login(self): + """ Last date logged in to server """ return str(self._last_login) @property def longest_session(self): + """ Longest session logged in """ return format_delta(self._longest_session, False) @property def messages(self): + """ Number of chat messages written """ return self._messages @property def time_per_message(self): + """ Average time between chat messages """ if self._messages == 0: return "
-
" return format_delta( @@ -269,14 +309,17 @@ def time_per_message(self): @property def death_count(self): + """ Number of recorded deaths """ return self._death_count @property def death_types(self): + """ Dictionary of deaths with death type as keys and the number of that death as values """ return sorted(self._death_types.items(), key=lambda k: k[1]) @property def pvp_kills(self): + """ Number of kills of other players """ kills = 0 for user in self._pvp_kills: kills += self._pvp_kills[user] @@ -284,63 +327,86 @@ def pvp_kills(self): @property def pvp_deaths(self): + """ Number of times killed by another player """ return self._pvp_deaths @property def achievement_count(self): + """ Number of recorded achivements """ return self._achievement_count @property def achievements(self): + """ List of achivements """ return sorted(self._achievements) @property def ragequit_count(self): + """Number of rage quits (if logout occurs seconds or less after death, see RAGEQUIT_MAX_ELAPSED_TIME) """ return self._ragequits @property def include_figures(self): + """ Boolean flag if figures are to be generated and included for this user """ return self._include_figures class ChatLog: + """ One chat message, the user typing it and the timestamp """ def __init__(self, timestamp, user, msg): + """ Constructor + @param timestamp: Time of chat message + @type timestamp: datetime.datetime + @param user : User typing this chat + @type user : String + @param msg : Message content + @type msg : String + """ self._time = str("%02d:%02d:%02d"%(timestamp.hour,timestamp.minute,timestamp.second)) self._user = user self._message = msg @property def time(self): + """ Time of message """ return self._time @property def user(self): + """ User typing the message """ return self._user @property def message(self): + """ Message content """ return self._message class ChatDay: + """ Container for all chat messages on one given day. Mainly used to format output html in a nice manner """ def __init__(self, timestamp): + """ Constructor + @param timestamp: Time of chat message + @type timestamp: datetime.date + """ self._date = str("%d-%02d-%02d"%(timestamp.year,timestamp.month,timestamp.day)) self._chat = [] self._even_day = False; - # list of all chat messages on this day @property def chat(self): + """ list of all chat messages on this day """ return self._chat - # date of chat history @property def date(self): + """ date of chat history """ return self._date - # tag every other day to increase readability on final document @property def even_day(self): + """ tag every other day to increase readability on final document """ return self._even_day class ServerStats: + """ Class containing all server-wide stats. Examples include accumulated playtime by all players and max online players """ def __init__(self): self._statistics_since = None self._time_played = datetime.timedelta() @@ -354,6 +420,11 @@ def __init__(self): self._hour_activity = [0]*24 def add_activity(self, user): + """ Add activity (playtime by date) from a single user + @param user: User with complete _day_play_minutes parsed from the log files + @type user: UserStats + @return : None + """ for d in user._day_activity: if d in self._day_play_minutes: self._day_play_minutes[d] += user._day_activity[d] @@ -365,40 +436,32 @@ def add_activity(self, user): self._hour_activity[i] += user._hour_activity[i] def make_plots(self, width, height): + """ Create images of server-wide use statistic and store these in the 'img' folder + @param width : Figure width in pixels + @type width : Int + @param height: Figure height in pixels + @type height: Int + @return : None + """ print 'Creating figures for server' # create figure holder used for all plots pylab.figure(1, figsize=(width/100.0, height/100.0)) - ### make daytime distribution plot + ### pre-evaluate a few variables needed to do the plotting # create x-axis time stamps - x = [] + clock_tag = [] # 24-hour clock tag for x-axis for i in range(24): - x.append(datetime.datetime(2001, 1,1, hour=i)) - # do the actual plot - pylab.bar(x, self._hour_activity, width=1.0/24.0) - # format to make it pretty - justHours = matplotlib.dates.DateFormatter('%H:%M') - pylab.setp(pylab.xticks()[1], rotation=20) - pylab.gca().xaxis.set_major_formatter(justHours) - pylab.xlabel('Clock'); - pylab.xlim(x[0], x[-1]) - pylab.ylabel('Minutes played'); - pylab.title('Daytime play distribution') - # save, clear and continue with the rest of the plots - pylab.savefig('img/server_daytime_dist.png') - pylab.clf() - - # playtime by day + clock_tag.append(datetime.datetime(2001, 1,1, hour=i)) today = datetime.date.today().toordinal() start_date = self._statistics_since.toordinal() - n_days = today - start_date + 1 - n_month = datetime.date.today().day - n_week = datetime.date.today().weekday() + 1 - playtime = [0]*n_days - users = [0]*n_days - pvp_kills= [0]*n_days - weektime = [0]*7 - date_tag = [] + n_days = today - start_date + 1 # days in recorded history + n_month = datetime.date.today().day # days in this month + n_week = datetime.date.today().weekday() + 1 # days in this week + playtime = [0]*n_days # play minutes per date + users = [0]*n_days # online users per date + pvp_kills= [0]*n_days # player vs player kills per date + weektime = [0]*7 # play minutes per weekday + date_tag = [] # date tags for x-axis on history plots for i in range(n_days): date_tag.append(datetime.datetime.fromordinal(start_date + i)) for date in self._day_play_minutes: @@ -409,33 +472,57 @@ def make_plots(self, width, height): for date in self._day_pvp_kills: pvp_kills[date-start_date] = self._day_pvp_kills[date]; - # playtime by day (all history) + ### make daytime distribution plot (play minutes by clock hour) + # do the actual plot + pylab.bar(clock_tag, self._hour_activity, width=1.0/24.0) + # format to make it pretty + justHours = matplotlib.dates.DateFormatter('%H:%M') + pylab.setp(pylab.xticks()[1], rotation=20) + pylab.gca().xaxis.set_major_formatter(justHours) + pylab.xlabel('Clock'); + pylab.xlim(clock_tag[0], clock_tag[-1]) + pylab.ylabel('Minutes played'); + pylab.title('Daytime play distribution') + # save, clear and continue with the rest of the plots + pylab.savefig('img/server_daytime_dist.png') + pylab.clf() + + ### make date playtime distribution plot (play minutes by date) + # do the actual plot pylab.plot(date_tag, playtime) + # format to make it pretty pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); pylab.ylabel('Minutes played'); pylab.title('Play minutes per day') + # save, clear and continue with the rest of the plots pylab.savefig('img/server_day_history.png') pylab.clf() - # active users by day + ### make online users plot (unique logged in users per date) + # do the actual plot pylab.plot(date_tag, users, 'm-') + # format to make it pretty pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); pylab.ylabel('Active players'); pylab.title('Online players per day') + # save, clear and continue with the rest of the plots pylab.savefig('img/server_day_users.png') pylab.clf() - # pvp kills per day + ### make hostility plot (pvp kills per date) + # do the actual plot pylab.plot(date_tag, pvp_kills, 'r-') + # format to make it pretty pylab.setp(pylab.xticks()[1], rotation=20) pylab.gca().xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%b %d %Y')) pylab.xlabel('Date'); pylab.ylabel('PvP Kills'); pylab.title('Server hostility: PvP kills per day') + # save, clear and continue with the rest of the plots pylab.savefig('img/server_day_pvp.png') pylab.clf() @@ -443,33 +530,47 @@ def make_plots(self, width, height): labels = 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' explode= [.03]*7 colors = ('#FF5050', '#009933', '#9966FF', '#996600', '#4D9494', '#C28585', '#3366CC') + # do the actual plot pylab.pie(weektime[::-1], explode=explode, labels=labels[::-1], autopct='%1.1f%%', shadow=True, colors=colors) - pylab.title('Playtime per weekday') #, bbox={'facecolor':'0.8', 'pad':5}) + # format to make it pretty + pylab.title('Playtime per weekday') + # save, clear and continue with the rest of the plots pylab.savefig('img/server_weekday_pie.png') pylab.clf() @property def statistics_since(self): + """ Date tag of first log entry """ return self._statistics_since @property def time_played(self): + """ Accumulated play time of all users """ return format_delta(self._time_played, True, True) @property def max_players(self): + """ Maximum users online at the same time """ return self._max_players @property def max_players_date(self): + """ Date when the maximum numbers of players were online """ return self._max_players_date @property def include_figures(self): + """ Boolean flag if figures are to be generated and included for server-wide statistics """ return self._include_figures def grep_logname_date(line): + """ Extract date from file name + @param line: filename starting with year-month-day + @type line: String + @return : The date tag + @rtype : datetime.date + """ try: d = time.strptime("-".join(line.split("-")[:3]), "%Y-%m-%d") except ValueError: @@ -478,6 +579,14 @@ def grep_logname_date(line): def grep_log_datetime(date, line): + """ Extract time from one log line + @param line: A single line from the log file + @type line: String + @param date: The current date + @type date: datetime.date + @return : The line date and time + @rtype : datetime.datetime + """ try: d = time.strptime(line.split(" ")[0], "[%H:%M:%S]") except ValueError: @@ -489,6 +598,12 @@ def grep_log_datetime(date, line): def grep_login_username(line): + """ Extract username from a login line in the log + @param line: A single line from the log file + @type line: String + @return : The username + @rtype : String + """ search = REGEX_LOGIN_USERNAME.search(line) if not search: print "### Warning: Unable to find login username:", line @@ -498,6 +613,12 @@ def grep_login_username(line): def grep_logout_username(line): + """ Extract username from a logout line in the log + @param line: A single line from the log file + @type line: String + @return : The username + @rtype : String + """ search = REGEX_LOGOUT_USERNAME.search(line) if not search: search = REGEX_LOGOUT_USERNAME2.search(line) @@ -509,6 +630,12 @@ def grep_logout_username(line): def grep_kick_username(line): + """ Extract username from a kick line in the log + @param line: A single line from the log file + @type line: String + @return : The username + @rtype : String + """ search = REGEX_KICK_USERNAME.search(line) if not search: print "### Warning: Unable to find kick logout username:", line @@ -517,6 +644,12 @@ def grep_kick_username(line): def grep_death(line): + """ Extract death info from a death line in the log. In case of PvP kill, extract killer from the last word + @param line: A single line from the log file + @type line: String + @return : Username, Death type, Last word (PvP victim if applicable) + @rtype : Tuple (String,String,String) + """ for regex in REGEX_DEATH_MESSAGES: search = regex.search(line) if search: @@ -528,6 +661,12 @@ def grep_chatlog(line): search def grep_achievement(line): + """ Extract achivement from a achivement award line in the log + @param line: A single line from the log file + @type line: String + @return : Username and achivement + @rtype : Tuple (String, String) + """ search = REGEX_ACHIEVEMENT.search(line) if not search: print "### Warning: Unable to find achievement username or achievement:", line @@ -535,8 +674,15 @@ def grep_achievement(line): username = search.group(1) return username.decode("ascii", "ignore").encode("ascii", "ignore"), search.group(2) -# returns number of minutes played during each clock hour and date between start and end def get_time_distribution(start, end): + """ Compute number of minutes played during each clock hour and date between start and end + @param start: login time + @type start: datetime.datetime + @param end : logout time + @type end : datetime.datetime + @return : Play minutes per hour, Play minutes per date + @rtype : Tuple (List of 24 Float, Dictionary(datetime.datetime, Float)) + """ hours = [0]*24 days = {} timeleft = end-start @@ -577,6 +723,16 @@ def parse_whitelist(whitelist_path): def parse_logs(logdir, since=None, whitelist_users=None): + """ Main function of the script. Parse all logs to generate user and server statistics + @param logdir : Directory of log files + @type logdir : String + @param since : logout time + @type since : datetime.datetime + @param whitelist_users : logout time + @type whitelist_users : datetime.datetime + @return : Stats for all users, the server and all chat messages + @rtype : Tuple (List of UserStats, ServerStats, List of ChatDay) + """ users = {} chat = [] server = ServerStats()