2 import sqlalchemy as sa
3 import sqlalchemy.sql.functions as func
4 import sqlalchemy.sql.expression as expr
5 from beaker.cache import cache_regions, cache_region
6 from collections import namedtuple
7 from datetime import datetime, timedelta
8 from pyramid.response import Response
9 from xonstat.models import *
10 from xonstat.util import *
11 from xonstat.views.helpers import RecentGame, recent_games_q
12 from webhelpers.paginate import Page
15 log = logging.getLogger(__name__)
18 @cache_region('hourly_term')
19 def get_summary_stats():
21 Gets the following aggregate or "summary" statistics about stats:
22 - the total number of players (total_players)
23 - the total number of servers (total_servers)
24 - the total number of games (total_games)
25 - the total number of dm games (dm_games)
26 - the total number of duel games (duel_games)
27 - the total number of ctf games (ctf_games)
29 It is worth noting that there is also a table built to house these
30 stats in case the query in this function becomes too long for the
31 one time it runs per hour. In that case there is a script in the
32 xonstatdb repo - update_summary_stats.sql - that can be used via
33 cron to update the data offline.
35 summary_stats = DBSession.query("total_players", "total_servers",
36 "total_games", "dm_games", "duel_games", "ctf_games").\
40 select game_type_cd, count(*) total_games
42 where game_type_cd in ('duel', 'dm', 'ctf')
46 select count(*) total_players
48 where active_ind = true
51 select count(*) total_servers
53 where active_ind = true
55 select tp.total_players, ts.total_servers, dm.total_games+
56 duel.total_games+ctf.total_games total_games,
57 dm.total_games dm_games, duel.total_games duel_games,
58 ctf.total_games ctf_games
59 from total_games dm, total_games duel, total_games ctf,
60 total_players tp, total_servers ts
61 where dm.game_type_cd = 'dm'
62 and ctf.game_type_cd = 'ctf'
63 and duel.game_type_cd = 'duel'
70 @cache_region('hourly_term')
71 def get_day_summary_stats():
73 Gets the following aggregate statistics about the past 24 hours:
74 - the number of active players (day_active_players)
75 - the number of games per game type (day_games)
77 This information is then summarized into a string which is passed
78 directly to the template.
81 # only games played during this range are considered
82 right_now = datetime.now()
83 cutoff_dt = right_now - timedelta(days=1)
85 games = DBSession.query(Game.game_type_cd, func.count()).\
86 filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
87 group_by(Game.game_type_cd).\
88 order_by(expr.desc(func.count())).all()
92 total_games += total[1]
98 other_games += total[1]
102 active_players = DBSession.query(func.count(sa.distinct(PlayerGameStat.player_id))).\
103 filter(PlayerGameStat.player_id > 2).\
104 filter(expr.between(PlayerGameStat.create_dt, cutoff_dt, right_now)).\
107 # don't send anything if we don't have any activity
111 in_paren = ", ".join(["{} {}".format(
112 g[1], g[0]) for g in games[:5]]
115 in_paren += ", {} other".format(other_games)
117 day_stat_line = "{} active players and {} games ({}) in the past 24 hours.".format(
123 except Exception as e:
128 @cache_region('hourly_term')
129 def get_ranks(game_type_cd):
131 Gets a set number of the top-ranked people for the specified game_type_cd.
133 The game_type_cd parameter is the type to fetch. Currently limited to
134 duel, dm, ctf, and tdm.
136 # how many ranks we want to fetch
137 leaderboard_count = 10
139 # only a few game modes are actually ranked
140 if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
143 ranks = DBSession.query(PlayerRank).\
144 filter(PlayerRank.game_type_cd==game_type_cd).\
145 order_by(PlayerRank.rank).\
146 limit(leaderboard_count).all()
151 def top_players_by_time_q(cutoff_days):
153 Query for the top players by the amount of time played during a date range.
155 Games older than cutoff_days days old are ignored.
158 # only games played during this range are considered
159 right_now = datetime.utcnow()
160 cutoff_dt = right_now - timedelta(days=cutoff_days)
162 top_players_q = DBSession.query(Player.player_id, Player.nick,
163 func.sum(PlayerGameStat.alivetime)).\
164 filter(Player.player_id == PlayerGameStat.player_id).\
165 filter(Player.player_id > 2).\
166 filter(expr.between(PlayerGameStat.create_dt, cutoff_dt, right_now)).\
167 order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
168 group_by(Player.nick).\
169 group_by(Player.player_id)
174 @cache_region('hourly_term')
175 def get_top_players_by_time(cutoff_days):
177 The top players by the amount of time played during a date range.
179 Games older than cutoff_days days old are ignored.
181 # how many to retrieve
184 # only games played during this range are considered
185 right_now = datetime.utcnow()
186 cutoff_dt = right_now - timedelta(days=cutoff_days)
188 top_players_q = top_players_by_time_q(cutoff_days)
190 top_players = top_players_q.limit(count).all()
192 top_players = [(player_id, html_colors(nick), score) \
193 for (player_id, nick, score) in top_players]
198 def top_servers_by_players_q(cutoff_days):
200 Query to get the top servers by the amount of players active
203 Games older than cutoff_days days old are ignored.
205 # only games played during this range are considered
206 right_now = datetime.utcnow()
207 cutoff_dt = right_now - timedelta(days=cutoff_days)
209 top_servers_q = DBSession.query(Server.server_id, Server.name,
211 filter(Game.server_id==Server.server_id).\
212 filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
213 order_by(expr.desc(func.count(Game.game_id))).\
214 group_by(Server.server_id).\
215 group_by(Server.name)
220 @cache_region('hourly_term')
221 def get_top_servers_by_players(cutoff_days):
223 The top servers by the amount of players active during a date range.
225 Games older than cutoff_days days old are ignored.
227 # how many to retrieve
230 top_servers = top_servers_by_players_q(cutoff_days).limit(count).all()
235 def top_maps_by_times_played_q(cutoff_days):
237 Query to retrieve the top maps by the amount of times it was played
240 Games older than cutoff_days days old are ignored.
242 # only games played during this range are considered
243 right_now = datetime.utcnow()
244 cutoff_dt = right_now - timedelta(days=cutoff_days)
246 top_maps_q = DBSession.query(Game.map_id, Map.name,
248 filter(Map.map_id==Game.map_id).\
249 filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
250 order_by(expr.desc(func.count())).\
251 group_by(Game.map_id).\
257 @cache_region('hourly_term')
258 def get_top_maps_by_times_played(cutoff_days):
260 The top maps by the amount of times it was played during a date range.
262 Games older than cutoff_days days old are ignored.
264 # how many to retrieve
267 top_maps = top_maps_by_times_played_q(cutoff_days).limit(count).all()
272 def _main_index_data(request):
274 leaderboard_lifetime = int(
275 request.registry.settings['xonstat.leaderboard_lifetime'])
277 leaderboard_lifetime = 30
279 leaderboard_count = 10
280 recent_games_count = 20
282 # summary statistics for the tagline
284 summary_stats = get_summary_stats()
285 day_stat_line = get_day_summary_stats()
291 # the three top ranks tables
293 for gtc in ['duel', 'ctf', 'dm', 'tdm']:
294 rank = get_ranks(gtc)
298 right_now = datetime.utcnow()
299 back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
301 # top players by playing time
302 top_players = get_top_players_by_time(leaderboard_lifetime)
304 # top servers by number of total players played
305 top_servers = get_top_servers_by_players(leaderboard_lifetime)
307 # top maps by total times played
308 top_maps = get_top_maps_by_times_played(leaderboard_lifetime)
310 # recent games played in descending order
311 rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
312 recent_games = [RecentGame(row) for row in rgs]
314 return {'top_players':top_players,
315 'top_servers':top_servers,
317 'recent_games':recent_games,
319 'summary_stats':summary_stats,
320 'day_stat_line':day_stat_line,
324 def main_index(request):
326 Display the main page information.
328 mainindex_data = _main_index_data(request)
330 # FIXME: code clone, should get these from _main_index_data
331 leaderboard_count = 10
332 recent_games_count = 20
334 for i in range(leaderboard_count-len(mainindex_data['top_players'])):
335 mainindex_data['top_players'].append(('-', '-', '-'))
337 for i in range(leaderboard_count-len(mainindex_data['top_servers'])):
338 mainindex_data['top_servers'].append(('-', '-', '-'))
340 for i in range(leaderboard_count-len(mainindex_data['top_maps'])):
341 mainindex_data['top_maps'].append(('-', '-', '-'))
343 return mainindex_data
346 def main_index_json(request):
348 JSON output of the main page information.
350 return [{'status':'not implemented'}]
353 def top_players_by_time(request):
354 current_page = request.params.get('page', 1)
356 cutoff_days = int(request.registry.settings.\
357 get('xonstat.leaderboard_lifetime', 30))
359 top_players_q = top_players_by_time_q(cutoff_days)
361 top_players = Page(top_players_q, current_page, items_per_page=25, url=page_url)
363 top_players.items = [(player_id, html_colors(nick), score) \
364 for (player_id, nick, score) in top_players.items]
366 return {'top_players':top_players}
369 def top_servers_by_players(request):
370 current_page = request.params.get('page', 1)
372 cutoff_days = int(request.registry.settings.\
373 get('xonstat.leaderboard_lifetime', 30))
375 top_servers_q = top_servers_by_players_q(cutoff_days)
377 top_servers = Page(top_servers_q, current_page, items_per_page=25, url=page_url)
379 return {'top_servers':top_servers}
382 def top_maps_by_times_played(request):
383 current_page = request.params.get('page', 1)
385 cutoff_days = int(request.registry.settings.\
386 get('xonstat.leaderboard_lifetime', 30))
388 top_maps_q = top_maps_by_times_played_q(cutoff_days)
390 top_maps = Page(top_maps_q, current_page, items_per_page=25, url=page_url)
392 return {'top_maps':top_maps}